Search code examples
javascriptcanvasgraphicsgeometrycurve

Drawing curve with canvas


I am working on a [specialized] graphics editor. It is used to draw simple vector graphics elements including arcs/curves.

We support a shape that mixes straight lines and curves. It gets drawn as a bunch of straight sections. At a later point, a designer can convert a straight segment into a curve. It looks like the below: enter image description here

We persist this object as an array of dots. Each dot has coordinates x and y as well as optional angles r1 and r2. I draw the shape on canvas using the below:

        const parts = [ 
                      ...,
                      {x: 359, y: 187, r1: 0.4, r2: 0},
                      {x: 214, y: 184, r1: 0, r2: 0.5}
                      ...
        ];

        for (let j = 0, len = parts.length; j < len; j++) {
            const p1 = parts[j];
            const p2 = parts[j < len - 1 ? j + 1 : 0];
            if ('r1' in p && p.r1 > 0 && 'r2' in p2 && p2.r2 > 0) {
                console.log('point:', p1);
                console.log('next point:', p2);
                console.log('draw arc, angle:', p1['r1']);
                console.log('next vertex angle:', p2['r2']);
                ctx[j ? 'quadraticCurveTo' : 'moveTo'](p1.x + 50, p1.y + 50, p1.x, p1.y);
            } else {
                ctx[j ? 'lineTo' : 'moveTo'](p1.x, p1.y);
            }
        }

It works, I get straight lines and curves when I need them.

However, my control point is off. I temporarily coded it as p1.x + 50, p1.y + 50 and this is not right. Knowing p1 and p2, including coordinates and angles, how do I calculate the control point?


Solution

  • You need to calculate intersection point of two rays. Using parametric representation:

          first ray              second ray
    x = p1.x + t * cos(r1) = p2.x + u * cos(r2)
    y = p1.y + t * sin(r1) = p1.y + u * sin(r2)
    

    Solve this equation system (shortening variable names as p1.x=x1, cos(r2)=c2 etx )

    x1 + t * c1 = x2 + u * c2 
    y1 + t * s1 = y2 + u * s2 
    
    t = (x2 - x1 + u * c2) / c1
    y1 + (x2 - x1 + u * c2) / c1 * s1 = y2 + u * s2 
    u  *  (c2 / c1 * s1 - s2) = (y2 - y1) -  (x2 - x1) / c1 * s1
    u  *  (c2 * s1 - s2 * c1 ) = (y2 - y1) * c1 -  (x2 - x1) * s1
    
    u = ((y2 - y1) * c1 -  (x2 - x1) * s1) / (c2 * s1 - s2 * c1 )
    

    substitute this value into the first equations and get x,y of intersection

    x = x2 + u * c2
    y = y2 + u * s2
    

    Zero value of denominator (c2 * s1 - s2 * c1) occurs for parallel vectors. Negative value of parameter u - for case of non-intersecting vectors when angle is too large. These cases are impossible when non-degenerate point sequence is used as input.