Friday, 29 April 2022

How to get a better approximation of a thick bezier curve?

Let's say I already have a bezier curve approximated by many straight lines (the bezier array in the code), and I would like to draw it with a series of rectangles. I have the following code below that does exactly this:

// don't change this array
const bezier = [{x:167.00,y:40.00},{x:154.37,y:42.09},{x:143.09,y:44.48},{x:133.08,y:47.15},{x:124.26,y:50.09},{x:116.55,y:53.27},{x:109.87,y:56.68},{x:104.15,y:60.31},{x:99.32,y:64.14},{x:95.28,y:68.15},{x:91.97,y:72.34},{x:89.31,y:76.67},{x:87.22,y:81.14},{x:85.63,y:85.74},{x:84.44,y:90.43},{x:83.60,y:95.22},{x:83.02,y:100.08},{x:82.63,y:105.00},{x:82.33,y:109.96},{x:82.07,y:114.94},{x:81.76,y:119.94},{x:81.33,y:124.93},{x:80.69,y:129.89},{x:79.77,y:134.82},{x:78.49,y:139.70},{x:76.78,y:144.50},{x:74.55,y:149.22},{x:71.74,y:153.84},{x:68.25,y:158.34},{x:64.03,y:162.71},{x:58.97,y:166.93},{x:53.02,y:170.98},{x:46.10,y:174.86},{x:38.11,y:178.54},{x:29.00,y:182.00}];

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const thickness = 35;

function rotateCanvas(x, y, a) {
  ctx.translate(x, y);
  ctx.rotate(a);
  ctx.translate(-x, -y);
}

function drawRectangle(rX, rY, rW, rH, rA, color) {
  ctx.beginPath();
  rotateCanvas(rX + rW / 2, rY + rH / 2, rA);
  ctx.rect(rX, rY, rW, rH);
  rotateCanvas(rX + rW / 2, rY + rH / 2, -rA);
  ctx.fill();
}

function calcRectFromLine(x1, y1, x2, y2) {
  const dx = x2 - x1;
  const dy = y2 - y1;
  const mag = Math.sqrt(dx * dx + dy * dy);
  const angle = Math.atan2(dy, dx);

  return { 
    x: (x1 + x2) / 2 - mag / 2,
    y: (y1 + y2) / 2 - thickness / 2,
    w: mag,
    h: thickness,
    a: angle
  };
}

function calculateRectangles() {
  const result = [];

  for (let i = 1; i < bezier.length; i++) {
    const prev = bezier[i - 1];
    const curr = bezier[i];

    result.push(calcRectFromLine(prev.x, prev.y, curr.x, curr.y));
  }

  return result;
}

const rectangles = calculateRectangles();

for (let r of rectangles) {
  drawRectangle(r.x, r.y, r.w, r.h, r.a);
}
<canvas width="400" height="400"></canvas>

If you run the snippet you'll see that the curve is not fully thick, and the fact that it is a series of rectangles is very obvious.

If you change the thickness parameter from 35 to a lower number and re-run it, it looks fine. It's only when it's very thick does this occur.

The code currently takes the bezier array, and creates a series of rotated rectangles and then renders them.

Is there any way to modify the calculateRectangles function to return a better approximation of the curve? Ideally it would still return a list of rectangles rotated around their center, but when rendered it would look more like the curve, and less like a list of rectangles.

The only idea I could think of is to somehow return twice as many rectangles from calculateRectangles, where each one is inverted from the previous one, such that both sides of the line are filled in, and while I think that might work, it unfortunately has the side-effect of returning twice as many rectangles, which is undesirable and I would to avoid it if possible.



from How to get a better approximation of a thick bezier curve?

No comments:

Post a Comment