javascript - 如何设置贝塞尔曲线的控制点来弯曲一条线?
问题描述
我想用贝塞尔曲线画一条稍微弯曲的线。
这是贝塞尔曲线函数:
function bezierCurve(controlPoint1, controlPoint2, line) { }
我不知道如何设置控制点以实现任何线条的略微弯曲。
我试图找到这条线的垂直点:
function Line(a, b) {
let mid = [(a[0] + b[0]) / 2,
(a[1] + b[1]) / 2];
let dX = b[0] - a[0],
dY = b[1] - a[1];
this.perpPoint = (y) => {
let x1 = mid[0],
y1 = mid[1],
x;
if (dX === 0 || dY === 0) {
x = y;
y = y1;
return [x, y];
}
let m = dX / dY;
let perpendicularM = - 1 / m;
// y - y1 = m(x - x1)
// x = (y - y1) / m + x1
x = (y - y1) / m + x1;
return [x, y];
};
并使用它以某种方式找到线中点上方的点作为控制点,但没有用。
解决方案
功能
function bezierCurve(controlPoint1, controlPoint2, line) { }
建议曲线是三次贝塞尔曲线(两个控制点),您给出的函数看起来只是在中间找到一个点并偏移一些固定量。您将需要更多地控制点与线的关系。
从线垂直偏移。
以下功能将为您提供更多控制权,让您指定沿线的哪个位置想要该点以及该点应离该线多远。
参数along
和dist
是直线的分数,例如along = 0.5
是一半,dist = 0.2
是直线长度的 1/5。dist > 0
并且该点偏移到线的右侧dist < 0
并且偏移量在左侧(Y轴指向下方)
function pointFromLine(along, dist, p1, p2, res = {}) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
res.x = p1.x + dx * along - dy * dist;
res.y = p1.y + dy * along + dx * dist;
return res;
}
找到距离中心线长度 1/5 的中点
const p1 = {x: 10, y: 10};
const p2 = {x: 510, y: 310};
const offset = pointFromLine(0.5, 0.2, p1, p2);
找到三次贝塞尔曲线的两个控制点。
const p1 = {x: 10, y: 10};
const p2 = {x: 510, y: 310};
const cp1 = pointFromLine(0.333, 0.2, p1, p2); // 1/3rd dist from start
const cp2 = pointFromLine(1 - 0.333, 0.2, p1, p2); // equal dist (1/3rd) from end of line
使用示例
以下代码段显示了用于为三次贝塞尔曲线创建对称曲线的函数。移动滑块以控制沿线的距离和距离线的偏移量
function pointFromLine(along, dist, p1, p2, res = {}) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
res.x = p1.x + dx * along - dy * dist;
res.y = p1.y + dy * along + dx * dist;
return res;
}
const p1 = {x: 40, y: 40};
const p2 = {x: 160, y: 160};
const cp1 ={x: 0, y: 0};
const cp2 ={x: 0, y: 0};
const ctx = canvas.getContext("2d");
update();
function drawPoint(p) {
ctx.beginPath();
ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
ctx.fill();
}
function drawLine(p1, p2) {
ctx.lineWidth = 1;
ctx.beginPath();
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
function drawCubicBezier(p1, p2, cp1, cp2) {
ctx.lineWidth = 3;
ctx.beginPath();
ctx.lineTo(p1.x, p1.y);
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y);
ctx.stroke();
drawLine(p1,cp1);
drawLine(p2,cp2);
drawPoint(p1);
drawPoint(p2);
drawPoint(cp1);
drawPoint(cp2);
}
alongIn.addEventListener("input", update);
distIn.addEventListener("input", update);
function update() {
const a = alongIn.value * 1;
const d = distIn.value * 1;
info.textContent = `Along: ${a.toFixed(2)} Offset: ${d.toFixed(2)}`
ctx.clearRect(0,0,200,200);
drawCubicBezier(
p1,
p2,
pointFromLine(a , d, p1, p2, cp1),
pointFromLine(1 - a, d, p1, p2, cp2)
);
}
canvas {
border: 1px solid black;
}
Dist along: <input id="alongIn" type="range" min="-1" max="2" step="0.01" value="0.3"/>
Dist out: <input id="distIn" type="range" min="-1" max="2" step="0.01" value="0.3"/>
<div id="info"> </div>
<canvas id="canvas" width="200" height="200" ></canvas>