Search code examples
javascriptcanvasgeometrybezier

Draw squares along a randomly generated curve


I'm drawing a curved line from the left side of the screen to the right side of the screen and then drawing 5 squares along that curve in random positions.

enter image description here

The problem occurs if I change the y-value of the curved line's END-POINT.

For example: if the line starts on the left at point (0, 200) and ends on the right at (1000, 200) - meaning the y-coordinate has the exact same value at both points, everything is fine. But if I change the end-point's y-value, everything gets thrown off - and I don't know how to properly compensate/calibrate for that change.

Working JSFiddle is here: https://jsfiddle.net/soren74/sy61z35g/6/

// Get the Canvas element
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");



function init() {
  // INIT the starting (x, y) coordinate of the first line at the center of the left side of the canvas
  const startX = 0;

  const startY = canvas.height / 2;

  const endX = canvas.width;
  const endY = startY;

  // PROBLEM STARTS IF I DO THE FOLLOWING:
  // const endY = startY + 50;

  // Addind a random +1 or -1 var - to be used by the Control Points:
  let randall = Math.floor(Math.random() * 100) % 2;
  let deltaY = randall == 0 ? 1 : -1;

  // I. Set the Curved Line's CONTROL POINTS:
  // Control Point # 1:
  const cp1X = 150 + Math.floor(Math.random() * 200);
  const cp1Y = startY - 250 * deltaY;
  console.log("\n>Control Pt. 1 = (" + cp1X + ", " + cp1Y + ")");

  // Control Point # 2:
  const cp2X = Math.floor(Math.random() * 500);
  const cp2Y = startY + 200 * deltaY;
  console.log("\n>Control Pt. 2 = (" + cp2X + ", " + cp2Y + ")");


  //
  //  II. DRAW the LINE
  // 
  ctx.beginPath();
  ctx.moveTo(startX, startY);
  ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, endX, endY);
  ctx.lineWidth = 2;
  ctx.strokeStyle = "black";
  ctx.shadowBlur = 8;
  ctx.shadowColor = "white";
  ctx.stroke();

  // Randomly place 5 squares along the curved Line:
  for (var squareCounter = 0; squareCounter < 5; squareCounter++) {
    const t = Math.random();
    const x = (1 - t) ** 3 * 0 + 3 * (1 - t) ** 2 * t * cp1X + 3 * (1 - t) * t ** 2 * cp2X + t ** 3 * canvas.width;
    const y = (1 - t) ** 3 * canvas.height / 2 + 3 * (1 - t) ** 2 * t * cp1Y + 3 * (1 - t) * t ** 2 * cp2Y + t ** 3 * canvas.height / 2;
    ctx.fillStyle = "blue";
    ctx.fillRect(x - 4, y - 4, 8, 8);
  }


}

init();
canvas {
  border: 1px solid black;
}
<canvas id="myCanvas" width="600" height="300"></canvas>


Solution

  • This happens because in your formula for calculating the y coordinate, you don't use endY, and so changing the value of endY does not influence the result (while it should).

    So remove all references to canvas.height and canvas.width in that formula and use the actual start/end coordinates:

    const x = (1-t)**3*startX + 3*(1-t)**2*t*cp1X + 3*(1-t)*t**2*cp2X + t**3*endX;
    const y = (1-t)**3*startY + 3*(1-t)**2*t*cp1Y + 3*(1-t)*t**2*cp2Y + t**3*endY;
    

    // Get the Canvas element
    const canvas = document.getElementById("myCanvas");
    const ctx = canvas.getContext("2d");
    
    function init() {
        // INIT the starting (x, y) coordinate of the first line 
        //    at the center of the left side of the canvas
        const startX = 0;
        const startY = canvas.height / 3;
        const endX = canvas.width;
        const endY = 2 * startY; // Different than startY
    
        // Addind a random +1 or -1 var - to be used by the Control Points:
        let deltaY = Math.floor(Math.random() * 2) * 2 - 1; 
    
        // I. Set the Curved Line's CONTROL POINTS:
        // Control Point # 1:
        const cp1X = 150 + Math.floor(Math.random() * 200);
        const cp1Y = -50; // edited for this snippet only
    
        // Control Point # 2:
        const cp2X = Math.floor(Math.random() * 500);
        const cp2Y = canvas.height + 50; // edited for this snippet only
    
        //  II. DRAW the LINE
        ctx.beginPath();
        ctx.moveTo(startX, startY);
        ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, endX, endY);
        ctx.lineWidth = 2;
        ctx.strokeStyle = "black";
        ctx.shadowBlur = 8;
        ctx.shadowColor = "white";
        ctx.stroke();
    
        // Randomly place 5 squares along the curved Line:
        for(var squareCounter = 0; squareCounter < 5; squareCounter++) {
           const t = Math.random(), tt = 1 - t;
           const x = tt ** 3 * startX + 3 * tt * t * (tt * cp1X + t * cp2X) + t ** 3 * endX;
           const y = tt ** 3 * startY + 3 * tt * t * (tt * cp1Y + t * cp2Y) + t ** 3 * endY;
           ctx.fillStyle = "blue";
           ctx.fillRect(x - 4, y - 4, 8, 8);
        }
    }
    
    init();
    canvas { border: 1px solid black;}
    body { margin: 0 }
    <canvas id="myCanvas" width="600" height="190"></canvas>