计算画布中的同心圆弧

时间:2015-06-01 19:52:37

标签: javascript canvas bezier quadratic

我正在尝试从给定的弧(二次贝塞尔曲线)计算出2个同心圆弧(立方贝塞尔曲线)。我想我可以计算1/3和2/3的立方体的控制点,但它并不完全匹配。

  var u = 1 / 3; // fraction of curve where Px1 and Py1 are 
  var v = 2 / 3; // fraction of curve where Px2 and Py2 are 
  //Calculate control points (Cx1, Cy1, Cx2, Cy2)
  var a = 3 * (1 - u) * (1 - u) * u;
  var b = 3 * (1 - u) * u * u;
  var c = 3 * (1 - v) * (1 - v) * v;
  var d = 3 * (1 - v) * v * v;
  var det = a * d - b * c;
  var Qx1 = Px1 - ((1 - u) * (1 - u) * (1 - u) * Px0 + u * u * u * Px3);
  var Qy1 = Py1 - ((1 - u) * (1 - u) * (1 - u) * Py0 + u * u * u * Py3);
  var Qx2 = Px2 - ((1 - v) * (1 - v) * (1 - v) * Px0 + v * v * v * Px3);
  var Qy2 = Py2 - ((1 - v) * (1 - v) * (1 - v) * Py0 + v * v * v * Py3);
  var Cx1 = (d * Qx1 - b * Qx2) / det;
  var Cy1 = (d * Qy1 - b * Qy2) / det;
  var Cx2 = ((-c) * Qx1 + a * Qx2) / det;
  var Cy2 = ((-c) * Qy1 + a * Qy2) / det;
  ctx.beginPath();
  ctx.moveTo(Px0, Py0);
  ctx.bezierCurveTo(Cx1, Cy1, Cx2, Cy2, Px3, Py3);
  ctx.strokeStyle = "#0000FF";
  ctx.stroke();

控制点是否也取决于弧的半径或完全不同的东西?立方贝塞尔甚至是绘制同心圆弧的好选择吗?二次贝塞尔绝对不起作用,立方体肯定让我更接近我需要的东西。

这是链接: http://codepen.io/davidreed0/full/zGqPxQ/

使用位置滑块移动椭圆。

1 个答案:

答案 0 :(得分:0)

现在的问题是对要求有点不清楚。在任何情况下,这都是一种不需要太多计算的方法,但是利用绘制操作可视化与codepen中显示的相同。

主要步骤是:

  • 在离屏画布上:
  • 定义半径设置为绿色区域的线条粗细
  • 为行
  • 定义圆形大写字母
  • 用纯色绘制Bezier线
  • 将结果绘制到主画布中,并使用相对于蓝线粗细的各种偏移量。
  • 清除中心,您将拥有蓝色轮廓
  • 实施手动Bezier,以便在该形状内的任何位置绘制绿色弧/椭圆

可以扩展半径/直径。如果你需要可变半径,你可以使用贝塞尔公式将一系列蓝色弧线相互叠加。

证明的概念

这将逐步显示该过程。

第1步

在屏幕外的画布上(此处显示在屏幕上进行演示,我们将在下一步中切换):

step1snap

var c = document.querySelector("canvas"),
    ctx = c.getContext("2d"),
    dia = 90;                                        // diameter of graphics

ctx.strokeStyle = "blue";                            // color
ctx.lineWidth = dia;                                 // line-width = dia
ctx.lineCap = "round";                               // round caps

// draw bezier (quadratic, one control point)
ctx.moveTo(dia, dia);
ctx.quadraticCurveTo(300, 230, c.width - dia, dia);
ctx.stroke();
<canvas width=600 height=300></canvas>

完成。我们现在有了主要的形状。根据需要调整点数。

第2步

由于我们现在有主要形状,我们将使用这个形状创建轮廓:

  • 将其绘制到主画布偏移它的圆圈(主要区域周围的f.ex.8位置)
  • 使用comp敲出中心。模式“destination-out”只留下轮廓

step2snap

var c = document.querySelector("canvas"),
    co = document.createElement("canvas"),
    ctx = c.getContext("2d"),
    ctxo = co.getContext("2d"),
    dia = 90;

co.width = c.width;
co.height = c.height;

ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";

// draw bezier (quadratic here, one control point)
ctxo.moveTo(dia, dia);
ctxo.quadraticCurveTo(300, 230, c.width - dia, dia);
ctxo.stroke();

// draw multiple times to main canvas
var thickness = 1, angle = 0, step = Math.PI * 0.25;

for(; angle < Math.PI * 2; angle += step) {
  var x = thickness * Math.cos(angle),
      y = thickness * Math.sin(angle);
  ctx.drawImage(co, x, y);
}

// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);
<canvas width=600 height=300></canvas>

第3步

使用画布的自定义实现绘制绿色圆圈。首先,我们备份生成的蓝色轮廓的副本,以便我们可以在自由圆的顶部重绘它。我们可以重复使用离线画布,只需清除它并绘制结果(重置变换):

我们需要的唯一计算是二次贝塞尔曲线,我们在[0,1]范围内提供t得到一个点:

function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}

结果将是(使用更接近原始codepen的值):

step3snap

var c = document.querySelector("canvas"),
    co = document.createElement("canvas"),
    ctx = c.getContext("2d"),
    ctxo = co.getContext("2d"),
    radius = 150,
    dia = radius * 2;

co.width = c.width;
co.height = c.height;

ctxo.translate(2,2);           // to avoid clipping of edges in this demo
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";

// draw bezier (quadratic here, one control point)
ctxo.moveTo(radius, radius);
ctxo.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctxo.stroke();

// draw multiple times to main canvas
var thickness = 1, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
  var x = thickness * Math.cos(angle),
      y = thickness * Math.sin(angle);
  ctx.drawImage(co, x, y);
}

// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);

// back-up result by reusing off-screen canvas
ctxo.clearRect(0, 0, co.width, co.height);
ctxo.drawImage(c, 0, 0);

// Step 3: draw the green circle at any point
ctx.globalCompositeOperation = "source-over";  // normal comp. mode
ctx.fillStyle = "#9f9";
ctx.strokeStyle = "#090";

var t = 0, dlt = 0.01;

(function loop(){
  
  ctx.clearRect(0, 0, c.width, c.height);
  t += dlt;
  
  // calc position based on t [0, 1] and the same points as for the blue
  var pos = getQuadraticPoint(radius, radius, 300, 230, c.width - radius, radius, t);
  
  // draw the arc
  ctx.beginPath();
  ctx.arc(pos.x + 2, pos.y + 2, radius, 0, 2*Math.PI);
  ctx.fill();
  
  // draw center line
  ctx.beginPath();
  ctx.moveTo(radius, radius);
  ctx.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
  ctx.stroke();

  // draw blue outline on top
  ctx.drawImage(co, 0, 0);
  
  if (t <0  || t >= 1) dlt = -dlt;  // ping-pong for demo
  requestAnimationFrame(loop);
})();

// formula for quadr. curve is: B(t) = (1-t)^2 * Z0 + 2(1-t)t * C + t^2 * Z1
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}
<canvas width=600 height=600></canvas>

按比例使用非1:1轴的示例:

var c = document.querySelector("canvas"),
    co = document.createElement("canvas"),
    ctx = c.getContext("2d"),
    ctxo = co.getContext("2d"),
    radius = 150,
    dia = radius * 2;

co.width = c.width;
co.height = c.height;

ctxo.translate(2,2);           // to avoid clipping of edges in this demo
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";

// draw bezier (quadratic here, one control point)
ctxo.moveTo(radius, radius);
ctxo.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctxo.stroke();

// draw multiple times to main canvas
var thickness = 2, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
  var x = thickness * Math.cos(angle),
      y = thickness * Math.sin(angle);
  ctx.drawImage(co, x, y);
}

// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);

// back-up result by reusing off-screen canvas
ctxo.setTransform(1,0,0,1,0,0);  // remove scale
ctxo.clearRect(0, 0, co.width, co.height);
ctxo.drawImage(c, 0, 0);

// Step 3: draw the green circle at any point
ctx.globalCompositeOperation = "source-over";  // normal comp. mode
ctx.fillStyle = "#9f9";
ctx.strokeStyle = "#090";

ctx.scale(1, 0.4);            // create ellipse

var t = 0, dlt = 0.01;

(function loop(){
  
  ctx.clearRect(0, 0, c.width, c.height * 1 / 0.4);
  t += dlt;
  
  // calc position based on t [0, 1] and the same points as for the blue
  var pos = getQuadraticPoint(radius, radius, 300, 230, c.width - radius, radius, t);
  
  // draw the arc
  ctx.beginPath();
  ctx.arc(pos.x + 2, pos.y + 2, radius, 0, 2*Math.PI);
  ctx.fill();
  
  // draw center line
  ctx.beginPath();
  ctx.moveTo(radius, radius);
  ctx.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
  ctx.stroke();

  // draw blue outline on top
  ctx.drawImage(co, 0, 0);
  
  if (t <0  || t >= 1) dlt = -dlt;  // ping-pong for demo
  requestAnimationFrame(loop);
})();

// formula for quadr. curve is: B(t) = (1-t)^2 * Z0 + 2(1-t)t * C + t^2 * Z1
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}
<canvas width=600 height=600></canvas>