我在 2D 中有三个点,我想绘制一条穿过它们的二次贝塞尔曲线。如何计算中间控制点(x1
和y1
quadTo 一样)?我从大学就知道线性代数,但需要一些简单的帮助。
如何计算中间控制点以使曲线也通过它?
让 P0、P1、P2 为控制点,Pc 为您希望曲线通过的固定点。
那么贝塞尔曲线定义为
P(t) = P0*t^2 + P1*2*t*(1-t) + P2*(1-t)^2
...其中 t 从零变为 1。
您的问题有无数个答案,因为对于任何 t 值,它都可能通过您的点...所以只需选择一个,例如 t=0.5,然后求解 P1:
Pc = P0*.25 + P1*2*.25 + P2*.25
P1 = (Pc - P0*.25 - P2*.25)/.5
= 2*Pc - P0/2 - P2/2
那里的“P”值是 (x,y) 对,所以只需对 x 和 y 应用一次方程:
x1 = 2*xc - x0/2 - x2/2
y1 = 2*yc - y0/2 - y2/2
...其中 (xc,yc) 是您希望它通过的点, (x0,y0) 是起点, (x2,y2) 是终点。这将为您提供在 t=0.5 时通过 (xc,yc) 的贝塞尔曲线。
我在我的 JavaFX 应用程序中使用了 Nemos 答案,但我的目标是绘制曲线,以便曲线的视觉转折点始终符合选择的固定点 (CP)。
CP = ControlPoint
SP = StartPoint
EP = EndPoint
BP(t) = BeziérCurve 上的变量点,其中 t 介于 0 和 1 之间
为了实现这一点,我做了一个变量 t(不固定 0.5)。如果选择的点 CP 不再位于 SP 和 EP 的中间,则您必须将 t 稍微向上或向下变化。作为第一步,您需要知道 CP 是否更接近 SP 或 EP:让 distanceSP 是 CP 和 SP 之间的距离, distanceEP 是 CP 和 EP 之间的距离,然后我将比率定义为:
ratio = (distanceSP - distanceEP) / (distanceSP + distanceEP);
现在我们将使用它来上下改变 t:
ratio = 0.5 - (1/3) * ratio;
注意:这仍然是一个近似值,1/3 是通过尝试和错误选择的。
这是我的 Java-Function:(Point2D 是 JavaFX 的一个类)
private Point2D adjustControlPoint(Point2D start, Point2D end, Point2D visualControlPoint) {
// CP = ControlPoint, SP = StartPoint, EP = EndPoint, BP(t) = variable Point on BeziérCurve where t is between 0 and 1
// BP(t) = SP*t^2 + CP*2*t*(1-t) + EP*(1-t)^2
// CP = (BP(t) - SP*t^2 - EP*(1-t)^2) / ( 2*t*(1-t) )
// but we are missing t the goal is to approximate t
double distanceStart = visualControlPoint.distance(start);
double distanceEnd = visualControlPoint.distance(end);
double ratio = (distanceStart - distanceEnd) / (distanceStart + distanceEnd);
// now approximate ratio to be t
ratio = 0.5 - (1.0 / 3) * ratio;
double ratioInv = 1 - ratio;
Point2D term2 = start.multiply( ratio * ratio );
Point2D term3 = end.multiply( ratioInv * ratioInv );
double term4 = 2 * ratio * ratioInv;
return visualControlPoint.subtract(term2).subtract(term3).multiply( 1 / term4);
}
我希望这有帮助。
让我们想要的四边形贝塞尔曲线为 P(t) = P1 t^2 + PC 2 t (1-t) + P2*(1-t)^2 并且四边形贝塞尔曲线通过抛出 P1,Pt,P2
通过三个点 P1、Pt、P3 的最佳四边形贝塞尔曲线具有控制点 PC,其张力指向曲线切线的垂线。该点也是该贝塞尔曲线的平分线。任何从 P1 开始并以 PC 结束的贝塞尔曲线在通过 throw PC 和 Pt 的矩形线上以相同的参数 t 值切割四边形贝塞尔曲线。
PC 点不是通过贝塞尔曲线的参数位置 t=.5 来实现的。一般来说,对于任何 P1、Pt、P2,我们都会得到一个 Pc,如下一个公式所述。
得到的 PC 也是该贝塞尔曲线与 Pt 的近点,穿过 Pt 和 Pc 的矩形线是三角形 P1、Pt、Pc 的平分线。
这是描述定理和公式的论文-在我的网站上https://microbians.com/math/Gabriel_Suchowolski_Quadratic_bezier_through_three_points_and_the_-equivalent_quadratic_bezier_(theorem)-.pdf
还有这里的代码
(function() {
var canvas, ctx, point, style, drag = null, dPoint;
// define initial points
function Init() {
point = {
p1: { x:200, y:350 },
p2: { x:600, y:350 }
};
point.cp1 = { x: 500, y: 200 };
// default styles
style = {
curve: { width: 2, color: "#333" },
cpline: { width: 1, color: "#C00" },
curve1: { width: 1, color: "#2f94e2" },
curve2: { width: 1, color: "#2f94e2" },
point: { radius: 10, width: 2, color: "#2f94e2", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI }
}
// line style defaults
ctx.lineCap = "round";
ctx.lineJoin = "round";
// event handlers
canvas.onmousedown = DragStart;
canvas.onmousemove = Dragging;
canvas.onmouseup = canvas.onmouseout = DragEnd;
DrawCanvas();
}
// draw canvas
function DrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// control lines
ctx.lineWidth = style.cpline.width;
ctx.strokeStyle = style.cpline.color;
ctx.beginPath();
ctx.moveTo(point.p1.x, point.p1.y);
ctx.lineTo(point.cp1.x, point.cp1.y);
ctx.lineTo(point.p2.x, point.p2.y);
ctx.stroke();
// curve
ctx.lineWidth = style.curve.width;
ctx.strokeStyle = style.curve.color;
ctx.beginPath();
ctx.moveTo(point.p1.x, point.p1.y);
through = !document.getElementById("cbThrough").checked;
if(through)
{
tmpx1 = point.p1.x-point.cp1.x;
tmpx2 = point.p2.x-point.cp1.x;
tmpy1 = point.p1.y-point.cp1.y;
tmpy2 = point.p2.y-point.cp1.y;
dist1 = Math.sqrt(tmpx1*tmpx1+tmpy1*tmpy1);
dist2 = Math.sqrt(tmpx2*tmpx2+tmpy2*tmpy2);
tmpx = point.cp1.x-Math.sqrt(dist1*dist2)*(tmpx1/dist1+tmpx2/dist2)/2;
tmpy = point.cp1.y-Math.sqrt(dist1*dist2)*(tmpy1/dist1+tmpy2/dist2)/2;
ctx.quadraticCurveTo(tmpx, tmpy, point.p2.x, point.p2.y);
}
else
{
ctx.quadraticCurveTo(point.cp1.x, point.cp1.y, point.p2.x, point.p2.y);
}
ctx.stroke();
//new, t range is [0, 1]
ctx.beginPath();
ctx.lineWidth = style.curve1.width;
ctx.strokeStyle = style.curve1.color;
ctx.moveTo(point.p1.x, point.p1.y);
// control points
for (var p in point) {
ctx.lineWidth = style.point.width;
ctx.strokeStyle = style.point.color;
ctx.fillStyle = style.point.fill;
ctx.beginPath();
ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true);
ctx.fill();
ctx.stroke();
}
}
// start dragging
function DragStart(e) {
e = MousePos(e);
var dx, dy;
for (var p in point) {
dx = point[p].x - e.x;
dy = point[p].y - e.y;
if ((dx * dx) + (dy * dy) < style.point.radius * style.point.radius) {
drag = p;
dPoint = e;
canvas.style.cursor = "move";
return;
}
}
}
// dragging
function Dragging(e) {
if (drag) {
e = MousePos(e);
point[drag].x += e.x - dPoint.x;
point[drag].y += e.y - dPoint.y;
dPoint = e;
DrawCanvas();
}
}
// end dragging
function DragEnd(e) {
drag = null;
canvas.style.cursor = "default";
DrawCanvas();
}
// event parser
function MousePos(event) {
event = (event ? event : window.event);
return {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
}
}
// start
canvas = document.getElementById("canvas");
if (canvas.getContext) {
ctx = canvas.getContext("2d");
Init();
}
})();
html, body { background-color: #DDD;font-family: sans-serif; height: 100%; margin:0;
padding:10px;
}
canvas { display:block;}
#btnControl { font-size:1em; position: absolute; top: 10px; left: 10px; }
#btnSplit { font-size:1em; position: absolute; top: 35px; left: 10px; }
#text { position: absolute; top: 75px; left: 10px; }
a {
text-decoration: none;
font-weight:700;
color: #2f94e2;
}
#little { font-size:.7em; color:#a0a0a0; position: absolute; top: 775px; left: 10px; }
<h1>Quadratic bezier throw 3 points</h1>
<div>
Also take a look the the math paper <a target="_blank" href="https://microbians.com/mathcode">Quadratic bezier through three points! →</a> <br/><br/>
Gabriel Suchowolski (<a href="https://microbians.com" target="_blank">microbians</a>), December, 2012
</div>
<div id="little">Thanks to 艾蔓草 xhhjin for the code (that I fork) implementing my math paper.</div>
<br/>
</div>
<input type="checkbox" id="cbThrough" name="through"/>Primitive quadratic Bezier (as control points)</input><br/><br/>
<canvas id="canvas" height="500" width="800" class="through" style="cursor: default; background-color: #FFF;"></canvas>
享受
如果您不想要确切的中间点,而是想要 t 的任何值(0 到 1),则等式为:
controlX = pointToPassThroughX/t - startX*t - endX*t;
controlY = pointToPassThroughY/t - startY*t - endY*t;
当然,这也适用于中点,只需将 t 设置为 0.5。简单的!:-)