Question

I have a quadratic curve drawn on html canvas using context.quadraticCurveTo(controlX, controlY, endX, endY); .

I have the control-point and the starting and end points, which are not necessarily level with each other horizontally.

How can I find the centre point on the curve using these parameters?

Actually I want to put a div tag on this center point. Is there any equation solving involved in this process?

Was it helpful?

Solution

quadraticCurveTo draws a quadratic Bézier curve.

The formulas to calculate the coordinates of a point at any given position (from 0 to 1) on the curve are

x(t) = (1-t)^2 * x1 + 2 * (1-t) * t * x2 + t^2 * x3
y(t) = (1-t)^2 * y1 + 2 * (1-t) * t * y2 + t^2 * y3

where (x1, y1) is the starting point, (x2, y2) is the control point and (x3, y3) is the end point.

So, turning that into JavaScript, we end up with something like

function _getQBezierValue(t, p1, p2, p3) {
    var iT = 1 - t;
    return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
}

function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
    return {
        x:  _getQBezierValue(position, startX, cpX, endX),
        y:  _getQBezierValue(position, startY, cpY, endY)
    };
}

If you pass the start, end and control points to getQuadraticCurvePoint there, along with 0.5 for the halfway position, you should get an object with the X and Y coordinates.

Example

function _getQBezierValue(t, p1, p2, p3) {
  var iT = 1 - t;
  return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
}

function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
  return {
    x: _getQBezierValue(position, startX, cpX, endX),
    y: _getQBezierValue(position, startY, cpY, endY),
  };
}

var position = 0.0;
var startPt = { x: 120, y: 10 };
var controlPt = { x: 410, y: 250 };
var endPt = { x: 10, y: 450 };
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
function drawNextPoint() {
  var pt = getQuadraticCurvePoint(
    startPt.x,
    startPt.y,
    controlPt.x,
    controlPt.y,
    endPt.x,
    endPt.y,
    position,
  );
  position = (position + 0.006) % 1.0;

  ctx.fillStyle = "rgba(255,0,0,0.5)";
  ctx.fillRect(pt.x - 2, pt.y - 2, 5, 5);
}

ctx.strokeStyle = "black";
ctx.moveTo(startPt.x, startPt.y);
ctx.quadraticCurveTo(controlPt.x, controlPt.y, endPt.x, endPt.y);
ctx.stroke();

setInterval(drawNextPoint, 40);
<canvas id="c" width="500" height="500">

OTHER TIPS

Here's a page describing the quadratic equation and it's solution: wiki page . And here is a good tutorial on the subject, complete with diagrams: tutorial

A possible way:

// compute coordinates of the middle point of a quadratic Bezier curve
// need two functions: quadraticBezierCurvePoint and quadraticBezierCurvesMiddle

function quadraticBezierCurvePoint(t, c) {
  // compute relative coordinates of a point on the curve using t and c
  // t is a number between 0 and 1
  // c is an array of 3 points:
  //     the initial point of the curve (always (0,0))
  //     the "handle" point of the curve
  //     the final point of the curve
  var t1, t1_2a, t1_2b, t1_2c;
  t1 = 1 - t;
  t1_2a = t1 * t1;
  t1_2b = (2 * t) * t1;
  t1_2c = t * t;
  return {
    x: (c[0].x * t1_2a) + (t1_2b * c[1].x) + (t1_2c * c[2].x),
    y: (c[0].y * t1_2a) + (t1_2b * c[1].y) + (t1_2c * c[2].y)
  };
}

function quadraticBezierCurvesMiddle(m, c) {
  var k, km = 1000,
    km2 = (km >> 1),
    len = 0,
    len2, x, y, a = new Array(km + 1);
  // compute curve lengths from start point to any point
  // store relative point coordinates and corresponding length in array a
  for (k = 0; k <= km; k++) {
    a[k] = {
      pt: quadraticBezierCurvePoint(k / km, c),
      len: 0
    };
    if (k > 0) {
      x = a[k].pt.x - a[k - 1].pt.x;
      y = a[k].pt.y - a[k - 1].pt.y;
      a[k].len = a[k - 1].len + Math.sqrt(x * x + y * y);
    }
  }
  // retrieve the point which is at a distance of half the whole curve length from start point
  // most of the time, this point is not the one at indice km2 in array a, but it is near it
  len2 = a[km].len / 2;
  if (a[km2].len > len2)
    for (k = km2; k >= 0; k--) {
      if (len2 >= a[k].len) break;
    } else
    for (k = km2; k <= km; k++) {
      if (len2 <= a[k].len) break;
    }
    // return absolute coordinates of the point
  return {
    x: Math.round((a[k].pt.x + m.x) * 100) / 100,
    y: Math.round((a[k].pt.y + m.y) * 100) / 100
  };
}

And the corresponding jsfiddle: jsfiddle.net/pTccL/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top