Draw a quadratic Bézier curve through three given points

Viewed 22125

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?

4 Answers

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

Related