Canvas Navigating along a Path

Canvas Navigating along a Path
Nikhil
Published on 2021-07-05 08:32:34

Canvas Navigating along a Path Find point on the curve


This example finds a point on a bezier or cubic curve at the position where the position is the unit distance on the curve 0 <= position <= 1. The position is clamped to the range thus if values < 0 or > 1 are passed they will be set 0,1 respectively.

Pass the function 6 coordinates for quadratic bezier or 8 for cubic. The last optional argument is the returned vector (point). If not given it will be created.

example:

var p1 = {x : 10 , y : 100};
var p2 = {x : 100, y : 200};
var p3 = {x : 200, y : 0};
var p4 = {x : 300, y : 100};
var point = {x : null, y : null};
// for cubic beziers
point = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, point);
// or No need to set point as it is a referance and will be set
getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, point);
// or to create a new point
var point1 = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y);
// for quadratic beziers
point = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, null, null, point);
// or No need to set point as it is a referance and will be set
getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, null, null, point);
// or to create a new point
var point1 = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);

The function

getPointOnCurve = function(position, x1, y1, x2, y2, x3, y3, [x4, y4], [vec])

Note: Arguments inside [x4, y4] are optional.
Note: x4,y4 if null, or undefined means that the curve is a quadratic bezier. vec is optional and will hold the returned point if supplied. If not it will be created.

var getPointOnCurve = function(position, x1, y1, x2, y2, x3, y3, x4, y4, vec) {
    var vec, quad;
    quad = false;
    if (vec === undefined) {
        vec = {};
    }
    if (x4 === undefined || x4 === null) {
        quad = true;
        x4 = x3;
        y4 = y3;
    }
    if (position <= 0) {
        vec.x = x1;
        vec.y = y1;
        return vec;
    }
    if (position >= 1) {
        vec.x = x4;
        vec.y = y4;
        return vec;
    }
    c = position;
    if (quad) {
        x1 += (x2 - x1) * c;
        y1 += (y2 - y1) * c;
        x2 += (x3 - x2) * c;
        y2 += (y3 - y2) * c;
        vec.x = x1 + (x2 - x1) * c;
        vec.y = y1 + (y2 - y1) * c;
        return vec;
    }
    x1 += (x2 - x1) * c;
    y1 += (y2 - y1) * c;
    x2 += (x3 - x2) * c;
    y2 += (y3 - y2) * c;
    x3 += (x4 - x3) * c;
    y3 += (y4 - y3) * c;
    x1 += (x2 - x1) * c;
    y1 += (y2 - y1) * c;
    x2 += (x3 - x2) * c;
    y2 += (y3 - y2) * c;
    vec.x = x1 + (x2 - x1) * c;
    vec.y = y1 + (y2 - y1) * c;
    return vec;
}

 

Finding extent of Quadratic Curve


When you need to find the bounding rectangle of a quadratic bezier curve you can use the following performant method.

// This method was discovered by Blindman67 and solves by first normalising the control point thereby
reducing the algorithm complexity
// x1,y1, x2,y2, x3,y3 Start, Control, and End coords of bezier
// [extent] is optional and if provided the extent will be added to it allowing you to use the
function
// to get the extent of many beziers.
// returns extent object (if not supplied a new extent is created)
// Extent object properties
// top, left,right,bottom,width,height
function getQuadraticCurevExtent(x1, y1, x2, y2, x3, y3, extent) {
    var brx, bx, x, bry, by, y, px, py;
    // solve quadratic for bounds by BM67 normalizing equation
    brx = x3 - x1; // get x range
    bx = x2 - x1; // get x control point offset
    x = bx / brx; // normalise control point which is used to check if maxima is in range
    // do the same for the y pointsbry = y3 - y1;
    by = y2 - y1;
    y = by / bry;
    px = x1; // set defaults in case maximas outside range
    py = y1;
    // find top/left, top/right, bottom/left, or bottom/right
    if (x < 0 || x > 1) { // check if x maxima is on the curve
        px = bx * bx / (2 * bx - brx) + x1; // get the x maxima
    }
    if (y < 0 || y > 1) { // same as x
        py = by * by / (2 * by - bry) + y1;
    }
    // create extent object and add extent
    if (extent === undefined) {
        extent = {};
        extent.left = Math.min(x1, x3, px);
        extent.top = Math.min(y1, y3, py);
        extent.right = Math.max(x1, x3, px);
        extent.bottom = Math.max(y1, y3, py);
    } else { // use spplied extent and extend it to fit this curve
        extent.left = Math.min(x1, x3, px, extent.left);
        extent.top = Math.min(y1, y3, py, extent.top);
        extent.right = Math.max(x1, x3, px, extent.right);
        extent.bottom = Math.max(y1, y3, py, extent.bottom);
    }
    extent.width = extent.right - extent.left;
    extent.height = extent.bottom - extent.top;
    return extent;
}

 

Finding points along a cubic Bezier curve


This example finds an array of approximately evenly spaced points along a cubic Bezier curve.

It decomposes Path segments created with context.bezierCurveTo into points along that curve.

// Return: an array of approximately evenly spaced points along a cubic Bezier curve
//
// Attribution: Stackoverflow's @Blindman67
// Cite:
http: //stackoverflow.com/questions/36637211/drawing-a-curved-line-in-css-or-canvas-and-moving-circlealong-
    it / 36827074 #36827074
// As modified from the above citation
//
// ptCount: sample this many points at interval along the curve
// pxTolerance: approximate spacing allowed between points
// Ax,Ay,Bx,By,Cx,Cy,Dx,Dy: control points defining the curve
//
function plotCBez(ptCount,pxTolerance,Ax,Ay,Bx,By,Cx,Cy,Dx,Dy){
var deltaBAx= Bx - Ax;
var deltaCBx = Cx - Bx;
var deltaDCx = Dx - Cx;
var deltaBAy = By - Ay;
var deltaCBy = Cy - By;
var deltaDCy = Dy - Cy;
var ax, ay, bx, by;
var lastX = -10000;
var lastY = -10000;
var pts = [{
    x: Ax,
    y: Ay
}];
for (var i = 1; i < ptCount; i++) {
    var t = i / ptCount;
    ax = Ax + deltaBAx * t;
    bx = Bx + deltaCBx * t;
    cx = Cx + deltaDCx * t;
    ax += (bx - ax) * t;
    bx += (cx - bx) * t;
    //
    ay = Ay + deltaBAy * t;
    by = By + deltaCBy * t;
    cy = Cy + deltaDCy * t;
    ay += (by - ay) * t;
    by += (cy - by) * t;
    var x = ax + (bx - ax) * t;
    var y = ay + (by - ay) * t;
    var dx = x - lastX;
    var dy = y - lastY;
    if (dx * dx + dy * dy > pxTolerance) {
        pts.push({
            x: x,
            y: y
        });
        lastX = x;
        lastY = y;
    }
}
pts.push({
    x: Dx,
    y: Dy
});
return (pts);
}

 

Finding points along a quadratic curve


This example finds an array of approximately evenly spaced points along a quadratic curve. It decomposes Path segments created with context.quadraticCurveTo into points along that curve.

// Return: an array of approximately evenly spaced points along a Quadratic curve
//
// Attribution: Stackoverflow's @Blindman67
// Cite:
http: //stackoverflow.com/questions/36637211/drawing-a-curved-line-in-css-or-canvas-and-moving-circlealong-
    it / 36827074 #36827074
// As modified from the above citation
//
// ptCount: sample this many points at interval along the curve
// pxTolerance: approximate spacing allowed between points
// Ax,Ay,Bx,By,Cx,Cy: control points defining the curve
//
function plotQBez(ptCount,pxTolerance,Ax,Ay,Bx,By,Cx,Cy){
var deltaBAx= Bx - Ax;
var deltaCBx = Cx - Bx;
var deltaBAy = By - Ay;
var deltaCBy = Cy - By;
var ax, ay;
var lastX = -10000;
var lastY = -10000;
var pts = [{
    x: Ax,
    y: Ay
}];
for (var i = 1; i < ptCount; i++) {
    var t = i / ptCount;
    ax = Ax + deltaBAx * t;
    ay = Ay + deltaBAy * t;
    var x = ax + ((Bx + deltaCBx * t) - ax) * t;
    var y = ay + ((By + deltaCBy * t) - ay) * t;
    var dx = x - lastX;
    var dy = y - lastY;
    if (dx * dx + dy * dy > pxTolerance) {
        pts.push({
            x: x,
            y: y
        });
        lastX = x;
        lastY = y;
    }
}
pts.push({
    x: Cx,
    y: Cy
});
return (pts);
}

 

Finding points along a line


This example finds an array of approximately evenly spaced points along a line. It decomposes Path segments created with context.lineTo into points along that line.

// Return: an array of approximately evenly spaced points along a line
//
// pxTolerance: approximate spacing allowed between points
// Ax,Ay,Bx,By: end points defining the line
//
function plotLine(pxTolerance, Ax, Ay, Bx, By) {
    var dx = Bx - Ax;
    var dy = By - Ay;
    var ptCount = parseInt(Math.sqrt(dx * dx + dy * dy)) * 3;
    var lastX = -10000;
    var lastY = -10000;
    var pts = [{
        x: Ax,
        y: Ay
    }];
    for (var i = 1; i <= ptCount; i++) {
        var t = i / ptCount;
        var x = Ax + dx * t;
        var y = Ay + dy * t;
        var dx1 = x - lastX;
        var dy1 = y - lastY;
        if (dx1 * dx1 + dy1 * dy1 > pxTolerance) {
            pts.push({
                x: x,
                y: y
            });
            lastX = x;
            lastY = y;
        }
    }
    pts.push({
        x: Bx,
        y: By
    });
    return (pts);
}

 

Finding points along an entire Path containing


curves and lines

This example finds an array of approximately evenly spaced points along an entire Path.It decomposes all Path segments created with context.lineTo, context.quadraticCurveTo and/or context.bezierCurveTo into points along that Path.

// Path related variables
var A = {
    x: 50,
    y: 100
};
var B = {
    x: 125,
    y: 25
};
var BB = {
    x: 150,
    y: 15
};
var BB2 = {
    x: 150,
    y: 185
};
var C = {
    x: 175,
    y: 200
};
var D = {
    x: 300,
    y: 150
};
var n = 1000;
var tolerance = 1.5;
var pts;
// canvas related variables
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = 378;
canvas.height = 256;
// Tell the Context to plot waypoint in addition to
// drawing the path
plotPathCommands(ctx, n, tolerance);
// Path drawing commands
ctx.beginPath();
ctx.moveTo(A.x, A.y);
ctx.bezierCurveTo(B.x, B.y, C.x, C.y, D.x, D.y);
ctx.quadraticCurveTo(BB.x, BB.y, A.x, A.y);
ctx.lineTo(D.x, D.y);
ctx.strokeStyle = 'gray';
ctx.stroke();
// Tell the Context to stop plotting waypoints
ctx.stopPlottingPathCommands();
// Demo: Incrementally draw the path using the plotted points
ptsToRects(ctx.getPathPoints());

function ptsToRects(pts) {
    ctx.fillStyle = 'red';
    var i = 0;
    requestAnimationFrame(animate);

    function animate() {
        ctx.fillRect(pts[i].x - 0.50, pts[i].y - 0.50, tolerance, tolerance);
        i++;
        if (i < pts.length) {
            requestAnimationFrame(animate);
        }
    }
}

A plug-in that automatically calculates points along the path

This code modifies these Canvas Context's drawing commands so the commands not only draw the line or curve, but also create an array of points along the entire path:

  • beginPath,
  • moveTo,
  • lineTo,
  • quadraticCurveTo,
  • bezierCurveTo.

 

Split bezier curves at position


This example splits cubic and bezier curves in two.

The function splitCurveAt splits the curve at position where 0.0 = start, 0.5 = middle, and 1 = end. It can split quadratic and cubic curves. The curve type is determined by the last x argument x4. If not undefined or null then it assumes the curve is cubic else the curve is a quadratic.

Example usage

Splitting quadratic bezier curve in two

var p1 = {
    x: 10,
    y: 100
};
var p2 = {
    x: 100,
    y: 200
};
var p3 = {
    x: 200,
    y: 0
};
var newCurves = splitCurveAt(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y)
var i = 0;
var p = newCurves
// Draw the 2 new curves
// Assumes ctx is canvas 2d context
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p[i++], p[i++]);
ctx.quadraticCurveTo(p[i++], p[i++], p[i++], p[i++]);
ctx.quadraticCurveTo(p[i++], p[i++], p[i++], p[i++]);
ctx.stroke();

Splitting cubic bezier curve in two

var p1 = {
    x: 10,
    y: 100
};
var p2 = {
    x: 100,
    y: 200
};
var p3 = {
    x: 200,
    y: 0
};
var p4 = {
    x: 300,
    y: 100
};
var newCurves = splitCurveAt(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y)
var i = 0;
var p = newCurves
// Draw the 2 new curves
// Assumes ctx is canvas 2d context
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p[i++], p[i++]);
ctx.bezierCurveTo(p[i++], p[i++], p[i++], p[i++], p[i++], p[i++]);
ctx.bezierCurveTo(p[i++], p[i++], p[i++], p[i++], p[i++], p[i++]);
ctx.stroke();

 

Trim bezier curve


This example show you how to trim a bezier.

The function trimBezier trims the ends off of the curve returning the curve fromPos to toPos. fromPos and toPos are in the range 0 to 1 inclusive, It can trim quadratic and cubic curves. The curve type is determined by the last x argument x4. If not undefined or null then it assumes the curve is cubic else the curve is a quadratic The trimmed curve is returned as an array of points. 6 points for quadratic curves and 8 for cubic curves.

example:

var trimBezier = function(fromPos, toPos, x1, y1, x2, y2, x3, y3, x4, y4) {
    var quad, i, s, retBez;
    quad = false;
    if (x4 === undefined || x4 === null) {
        quad = true; // this is a quadratic bezier
    }
    if (fromPos > toPos) { // swap is from is after to
        i = fromPos;
        fromPos = toPos
        toPos = i;
    }
    // clamp to on the curve
    toPos = toPos <= 0 ? 0 : toPos >= 1 ? 1 : toPos;
    fromPos = fromPos <= 0 ? 0 : fromPos >= 1 ? 1 : fromPos;
    if (toPos === fromPos) {
        s = splitBezierAt(toPos, x1, y1, x2, y2, x3, y3, x4, y4);
        i = quad ? 4 : 6;
        retBez = [s[i], s[i + 1], s[i], s[i + 1], s[i], s[i + 1]];
        if (!quad) {
            retBez.push(s[i], s[i + 1]);
        }
        return retBez;
    }
    if (toPos === 1 && fromPos === 0) { // no trimming required
        retBez = [x1, y1, x2, y2, x3, y3]; // return original bezier
        if (!quad) {
            retBez.push(x4, y4);
        }
        return retBez;
    }
    if (fromPos === 0) {
        if (toPos < 1) {
            s = splitBezierAt(toPos, x1, y1, x2, y2, x3, y3, x4, y4);
            i = 0;
            retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
            if (!quad) {
                retBez.push(s[i++], s[i++]);
            }
        }
        return retBez;
    }
    if (toPos === 1) {
        if (fromPos < 1) {
            s = splitBezierAt(toPos, x1, y1, x2, y2, x3, y3, x4, y4);
            i = quad ? 4 : 6;
            retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
            if (!quad) {
                retBez.push(s[i++], s[i++]);
            }
        }
        return retBez;
    }
    s = splitBezierAt(fromPos, x1, y1, x2, y2, x3, y3, x4, y4);
    if (quad) {
        i = 4;
        toPos = (toPos - fromPos) / (1 - fromPos);
        s = splitBezierAt(toPos, s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]);
        i = 0;
        retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
        return retBez;
    }
    i = 6;
    toPos = (toPos - fromPos) / (1 - fromPos);
    s = splitBezierAt(toPos, s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]);
    i = 0;
    retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
    return retBez;
}

 

Length of a Cubic Bezier Curve (a close approximation)


  • Method: The length of a cubic Bezier curve does not have a direct mathematical calculation. This "brute force" method finds a sampling of points along the curve and calculates the total distance spanned by those points.
  • Accuracy: The approximate length is 99+% accurate using the default sampling size of 40.

// Return: Close approximation of the length of a Cubic Bezier curve
//
// Ax,Ay,Bx,By,Cx,Cy,Dx,Dy: the 4 control points of the curve
// sampleCount [optional, default=40]: how many intervals to calculate
// Requires: cubicQxy (included below)
//
function cubicBezierLength(Ax, Ay, Bx, By, Cx, Cy, Dx, Dy, sampleCount) {
    var ptCount = sampleCount || 40;
    var totDist = 0;
    var lastX = Ax;
    var lastY = Ay;
    var dx, dy;
    for (var i = 1; i < ptCount; i++) {
        var pt = cubicQxy(i / ptCount, Ax, Ay, Bx, By, Cx, Cy, Dx, Dy);
        dx = pt.x - lastX;
        dy = pt.y - lastY;
        totDist += Math.sqrt(dx * dx + dy * dy);
        lastX = pt.x;
        lastY = pt.y;
    }
    dx = Dx - lastX;
    dy = Dy - lastY;
    totDist += Math.sqrt(dx * dx + dy * dy);
    return (parseInt(totDist));
}
// Return: an [x,y] point along a cubic Bezier curve at interval T
//
// Attribution: Stackoverflow's @Blindman67
// Cite:
http: //stackoverflow.com/questions/36637211/drawing-a-curved-line-in-css-or-canvas-and-moving-circlealong-
    it / 36827074 #36827074// As modified from the above citation
//
// t: an interval along the curve (0<= t <= 1)
// ax,ay,bx,by,cx,cy,dx,dy: control points defining the curve
//
function cubicQxy(t, ax, ay, bx, by, cx, cy, dx, dy) {
    ax += (bx - ax) * t;
    bx += (cx - bx) * t;
    cx += (dx - cx) * t;
    ax += (bx - ax) * t;
    bx += (cx - bx) * t;
    ay += (by - ay) * t;
    by += (cy - by) * t;
    cy += (dy - cy) * t;
    ay += (by - ay) * t;
    by += (cy - by) * t;
    return ({
        x: ax + (bx - ax) * t,
        y: ay + (by - ay) * t
    });
}

 

Length of a Quadratic Curve


Given the 3 points of a quadratic curve the following function returns the length.

function quadraticBezierLength(x1, y1, x2, y2, x3, y3)
var a, e, c, d, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = (a = 4 * (d * d + d1 * d1));
c1 += (b = 4 * (d * e + d1 * e1));
c1 += (c = e * e + e1 * e1);
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) / (4 * a1);
}

Derived from the quadratic bezier function F(t) = a * (1 - t)2 + 2 * b * (1 - t) * t + c * t2

ATutorialHub Related Guide

Comments (8)

Leave a Comment

Your email address will not be published. Required fields are marked*

User Comments

html tutorial comments

panduranga gupta

2021-07-05 07:03:13

good website for learning and help me a lot

html tutorial comments

raju

2021-09-25 14:58:47

The awsome website i am looking like for a long time, good work atutorialhub team keep doing

html tutorial comments

Shivani

2021-09-01 15:03:56

Learning a lot from the courses present on atutorialhub. The courses are very well explained. Great experience

html tutorial comments

Harshitha

2021-09-10 15:05:45

It is very helpful to students and easy to learn the concepts

html tutorial comments

Sowmya

2021-09-14 15:06:41

Great job Tutorials are easy to understand Please make use of it

html tutorial comments

Zain Khan

2021-09-18 15:07:23

Great content and customized courses.

html tutorial comments

Rudrakshi Bhatt

2021-09-09 15:08:10

Well structured coursed and explained really well!

html tutorial comments

Pavana Somashekar

2021-09-11 15:09:08

Good platform for beginners and learn a lot on this website