通过三个给定点绘制二次 Bézier 曲线

发布于 2024-11-24 04:07:28 字数 142 浏览 1 评论 0原文

我在 2D 中有三个点,我想绘制一条穿过它们的二次贝塞尔曲线。如何计算中间控制点(如quadTo 中的x1y1)?我在大学时了解线性代数,但需要一些简单的帮助。

如何计算中间控制点以使曲线也穿过它?

I have three points in 2D and I want to draw a quadratic Bézier curve passing through them. How do I calculate the middle control point (x1 and y1 as in quadTo)? I know linear algebra from college but need some simple help on this.

How can I calculate the middle control point so that the curve passes through it as well?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

╰沐子 2024-12-01 04:07:28

令 P0、P1、P2 为控制点,Pc 为您希望曲线经过的固定点。

那么贝塞尔曲线的定义是

P(t) = P0*t^2 + P1*2*t*(1-t) + P2*(1-t)^2

...其中 t 从 0 到 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) 的贝塞尔曲线。

Let P0, P1, P2 be the control points, and Pc be your fixed point you want the curve to pass through.

Then the Bezier curve is defined by

P(t) = P0*t^2 + P1*2*t*(1-t) + P2*(1-t)^2

...where t goes from zero to 1.

There are an infinite number of answers to your question, since it might pass through your point for any value of t... So just pick one, like t=0.5, and solve for P1:

Pc = P0*.25 + P1*2*.25 + P2*.25

P1 = (Pc - P0*.25 - P2*.25)/.5

   = 2*Pc - P0/2 - P2/2

There the "P" values are (x,y) pairs, so just apply the equation once for x and once for y:

x1 = 2*xc - x0/2 - x2/2
y1 = 2*yc - y0/2 - y2/2

...where (xc,yc) is the point you want it to pass through, (x0,y0) is the start point, and (x2,y2) is the end point. This will give you a Bezier that passes through (xc,yc) at t=0.5.

站稳脚跟 2024-12-01 04:07:28

令我们想要的四边形贝塞尔曲线为 P(t) = P1t^2 + PC2t(1-t) + P2*(1-t)^ 2
且四边形贝塞尔曲线经过投掷 P1,Pt,P2

通过三个点 P1,Pt,P3 的最佳四边形贝塞尔曲线具有控制点 PC,其张力指向曲线切线的垂直方向。该点也是该贝塞尔曲线的平分线。任何从 P1 开始并以 PC 结束的贝塞尔曲线位于通过抛出 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_third_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>

享受

Let the quad bezier we want to take as P(t) = P1t^2 + PC2t(1-t) + P2*(1-t)^2
and that quad bezier passing throw P1,Pt,P2

The best quad bezier that pass through the three points P1,Pt,P3 have the control point PC with tension directed in the perpendicular of the tangent of the curve. That point is also bisector of that bezier. Any bezier starting P1 and ending P3 with PC on the rect line that pass throw PC and Pt cuts the quad beziers at the same parametric t value.

The PC point is not achieved through the parametric position t=.5 of the bezier. In general for any P1,Pt,P2 the we got a Pc as described on the next formula.

enter image description here

The resulting PC is also the near point of that bezier to Pt and the rect line that pass through Pt and Pc is the bisector of the triangle P1, Pt, Pc.

Here is the paper where the theorem and formula is described -That is at my website https://microbians.com/math/Gabriel_Suchowolski_Quadratic_bezier_through_three_points_and_the_-equivalent_quadratic_bezier_(theorem)-.pdf

And also here is code

(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>

Enjoy

海夕 2024-12-01 04:07:28

我在我的 JavaFX 应用程序中使用了 Nemos 答案,但我的目标是绘制曲线,以便曲线的视觉转折点始终与所选的固定转折点 (CP) 相符。

CP = 控制点
SP = 起点
EP = 端点
BP(t) = 贝塞尔曲线上的变量点,其中 t 介于 0 和 1 之间

为了实现此目的,我创建了一个变量 t(不固定为 0.5)。如果选择的 CP 点不再位于 SP 和 EP 之间,则必须稍微向上或向下改变 t。
第一步,您需要知道 CP 是否更接近 SP 还是 EP:
令距离SP为CP和SP之间的距离
distanceEP 是 CP 和 EP 之间的距离
然后我将比率定义为:

ratio = (distanceSP - distanceEP) / (distanceSP + distanceEP);

现在我们将使用它来上下改变 t:

ratio = 0.5 - (1/3) * ratio;

注意:这仍然是一个近似值,1/3 是通过尝试和错误选择的。

这是我的 Java 函数:
(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);
}

我希望这会有所帮助。

I have used Nemos answer in my JavaFX apllication, but my goal was to draw the curve, so that the visual turning point of the curve always fits with the choosen fixed one (CP).

CP = ControlPoint
SP = StartPoint
EP = EndPoint
BP(t) = variable Point on BeziérCurve where t is between 0 and 1

To achieve this i made a variable t (not fix 0.5). If the choosen Point CP is no longer in the middle between SP and EP, you have to vary t up or down a little bit.
As a first step you need to know if CP is closer to SP or EP:
Let distanceSP be the distance between CP and SP
and distanceEP the distance between CP and EP
then i define ratio as:

ratio = (distanceSP - distanceEP) / (distanceSP + distanceEP);

Now we are going to use this to vary t up and down:

ratio = 0.5 - (1/3) * ratio;

note: This is still an approximation and 1/3 is choosen by try and error.

Here is my Java-Function:
(Point2D is a class of 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);
}

I hope this helps.

鹿童谣 2024-12-01 04:07:28

如果您不需要精确的中点,而是想要 t 的任何值(0 到 1),则方程为:

controlX = pointToPassThroughX/t - startX*t - endX*t;
controlY = pointToPassThroughY/t - startY*t - endY*t;

当然,这也适用于中点,只需将 t 设置为 0.5。简单的! :-)

If you don't want the exact middle point, rather you want any value for t (0 to 1), the equation is:

controlX = pointToPassThroughX/t - startX*t - endX*t;
controlY = pointToPassThroughY/t - startY*t - endY*t;

Of course, this will also work for the mid point, just set t to be 0.5. Simple! :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文