使用处理 (p5.js) 的二次曲线上的点

Points on a Quadratic Curve using Processing (p5.js)

我正在使用这个公式来计算二次曲线上的点:

  cPx2 = (1-t)*(1-t)* x1+2 * (1-t)*t*qcX + t*t*x2;
  cPy2 = (1-t)*(1-t)* y1+2 * (1-t)*t*qcY + t*t*y2;

当我设置 t = 10 并遍历曲线时,我得到:

看起来它正在获取曲线(花形)上的点,但也获取了“控制点”上的所有点。

我用这个公式来生成点数:

    flowerArray=[]
    for(let i = 0; i < numVertices+1; i++) {
    angle = i * spacing;
    x = centerX + cos(radians(angle)) * 180;
    y = centerY+ sin(radians(angle)) * 180;

    if(i == 0) {
      flowerArray.push(x,y);
    }else {
        cAngle = angle - spacing/2;
          cX = centerX + cos(radians(cAngle)) * 100;
          cY = centerY+  sin(radians(cAngle)) * 100;
      
    flowerArray.push(cX,cY,x,y)
    }
   }

问:是否可以只取“花”上的点,而不取其外形?

我尝试用几种不同的方式跳过数组,但我无法让它按照我希望的方式工作。

更新 我用这个来画点:

    for (i = 0; i < flowerArray.length; i+=2){
        x1=flowerArray[i] 
        y1=flowerArray[i+1]  
        qcX=flowerArray[i+2] 
        qcY=flowerArray[i+3] 
        x2=flowerArray[i+4]
        y2=flowerArray[i+5] 
    for (k=0; k<= steps; k++) {   
      t = k/steps
      cPx2 = (1-t)*(1-t)* x1+2 * (1-t)*t*qcX + t*t*x2;
      cPy2 = (1-t)*(1-t)* y1+2 * (1-t)*t*qcY + t*t*y2;
        circle(cPx2, cPy2,3);    
}
}

多么可爱的问题。

突出的一件事是这部分:

if(i == 0) {
      flowerArray.push(x,y);
    }else {
        cAngle = angle - spacing/2;
          cX = centerX + cos(radians(cAngle)) * 100;
          cY = centerY+  sin(radians(cAngle)) * 100;
      
    flowerArray.push(cX,cY,x,y)
    }

请注意,您调用了 flowerArray.push(x,y);,而在任何其他情况下,您都按了 4 而不是两个值:flowerArray.push(cX,cY,x,y)。目前还不清楚为什么这个条件首先是必要的:if(i == 0)

代码在没有它的情况下按预期工作:

function setup() {
  
  createCanvas(512, 512);
  background(226, 255, 204);
  
  let flowerArray = [];
  let centerX = 256;
  let centerY = 256;
  let numVertices = 7;
  let steps = 11;
  let spacing = 360 / numVertices;

  
  for (let i = 0; i < numVertices + 1; i++) {
    
    angle = i * spacing;
    
    x = centerX + cos(radians(angle)) * 180;
    y = centerY + sin(radians(angle)) * 180;
  
    cAngle = angle - spacing/2;
      
    cX = centerX + cos(radians(cAngle)) * 100;
    cY = centerY+  sin(radians(cAngle)) * 100;
  
    flowerArray.push(cX, cY, x, y);
  }

  for (i = 0; i < flowerArray.length; i+=2) {
    
    x1=flowerArray[i];
    y1=flowerArray[i+1];  
    
    qcX=flowerArray[i+2];
    qcY=flowerArray[i+3];
    
    x2=flowerArray[i+4];
    y2=flowerArray[i+5];
    
    for (k=0; k <= steps; k++) {
      t = k/steps;
      cPx2 = (1-t)*(1-t)* x1+2 * (1-t)*t*qcX + t*t*x2;
      cPy2 = (1-t)*(1-t)* y1+2 * (1-t)*t*qcY + t*t*y2;
      
      circle(cPx2, cPy2, 3);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

我个人建议养成格式化代码的习惯:这样可以更轻松地阅读代码和发现问题。您编写的程序越多,程序越大,您阅读代码的时间就越多,因此提高代码的可读性肯定会有所回报。

另一个建议是将quadratic bezier formula封装在一个函数中:

function quadLerp(p0, p1, p2, t){
  return ((1-t)*(1-t)) * p0 + 2 * ((1-t) * t * p1) + t * t * p2;
}

这样称呼它:

  cPx2 = quadLerp(x1, qcX, x2, t);
  cPy2 = quadLerp(y1, qcY, y2, t);

关于二次贝塞尔曲线的一个很酷的事情是您可以通过对两个线性插值进行插值来计算它们:

弦乐艺术中的二次贝塞尔曲线插图。在每种情况下,用黑色圆圈标记的端点和用 X 标记的控制点定义二次贝塞尔曲线,显示为 Wikipedia user Cmglee

的虚线

鉴于您可以通过 lerp() 在 p5.js 中计算线性插值,您可以将二次插值计算为:

function quadLerp(p0, p1, p2, t){
  return lerp(lerp(p0, p1, t),
              lerp(p1, p2, t),
              t);
}

很高兴 p5.js 支持各种曲线绘制函数,例如 bezier() or curve()(以及类似的函数,例如 bezierPoint()/curvePoint() 来计算您可以使用的插值用于自定义渲染)

更新 根据您的评论,我了解到您只想绘制内部形状。

您的代码正在处理正多边形的外部点和内部中点,绘制星形形状和下一个外部点,并将它们用作锚点/控制点以在这些点之间的二次贝塞尔曲线上绘制圆。好像这还不够复杂,有一个数组将所有锚点和控制点混合存储到一个列表中,您必须跟踪索引才能正确绘制。哦,你还使用极坐标系到笛卡尔坐标系的转换来首先绘制正则 polygon/star。

发生了很多事情,让我们试着分解一下。

从画星和它背后的数学开始:这类似于 islia 的问题,你可以看到我的

请注意她问题中的 star example:这是一个不错的起点,因为我们不必担心二次贝塞尔曲线点。它确实介绍了您可能还不熟悉的push()/pop()。知道它很有用,但现在可以跳过它。让我们看一下该片段的简化版本:

function setup() {
  createCanvas(512, 512);
}

function draw() {
  background(102);

  star(width * 0.5, height * 0.5, 80, 100, 7);
}

function star(x, y, radius1, radius2, npoints) {
  let angle = TWO_PI / npoints;
  let halfAngle = angle / 2.0;
  beginShape();
  for (let a = 0; a < TWO_PI; a += angle) {
    let sx = x + cos(a) * radius2;
    let sy = y + sin(a) * radius2;
    vertex(sx, sy);
    sx = x + cos(a + halfAngle) * radius1;
    sy = y + sin(a + halfAngle) * radius1;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

现在让我们看同样的东西更明显的变量名:

function setup() {
  createCanvas(512, 512);
}

function draw() {
  background(102);

  star(width * 0.5, height * 0.5, 80, 100, 7);
}

function star(x, y, innerRadius, outerRadius, npoints) {
  let angle = TWO_PI / npoints;
  let halfAngle = angle / 2.0;
  beginShape();
  
  for (let a = 0; a < TWO_PI; a += angle) {
    
    let xOuter = x + cos(a) * outerRadius;
    let yOuter = y + sin(a) * outerRadius;
    vertex(xOuter, yOuter);
    
    let xInner = x + cos(a + halfAngle) * innerRadius;
    let yInner = y + sin(a + halfAngle) * innerRadius;
    vertex(xInner, yInner);
  }
  
  endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

希望这能让你更容易理解哪个点是哪个点。

要绘制二次贝塞尔曲线点,您需要当前外部点和下一个外部点作为锚点,当前内部点(在它们之间,半径较小)作为控制点。

这是草图的修改版本,其中 star() 函数被重新用于绘制花朵:

function setup() {
  createCanvas(512, 512);
}

function draw() {
  background(226, 255, 204);

  flower(width * 0.5, height * 0.5, mouseX, 100, 7);
  
  text("innerRadius = " + mouseX, 10, 15);  
}

function flower(x, y, innerRadius, outerRadius, npoints) {
  let angleIncrement = TWO_PI / npoints;
  let halfAngle = angleIncrement / 2.0;
  // increment by point index
  for (let i = 0; i < npoints; i++) {
    // calculate the current angle around the circle
    let angle = angleIncrement * i;
    // calculate current outer point
    let xOuter = x + cos(angle) * outerRadius;
    let yOuter = y + sin(angle) * outerRadius;
    // calculate current inner point
    let xInner = x + cos(angle + halfAngle) * innerRadius;
    let yInner = y + sin(angle + halfAngle) * innerRadius;
    
    // next angle increment
    let angleNext = angleIncrement * (i+1);
    // calculate next outer point
    let xOuterNext = x + cos(angleNext) * outerRadius;
    let yOuterNext = y + sin(angleNext) * outerRadius;
    // draw quad bezier between current and outer points with inner point as control point
    quadBezierCircles(xOuter, yOuter, xInner, yInner, xOuterNext, yOuterNext, 11);
    
    // for debug purposes only: render 
    if(mouseIsPressed){
      circle(xInner,yInner,9);
      circle(xOuter,yOuter,9);
    }
  }
}

function quadBezierCircles(anchorX1, anchorY1, controlX, controlY, anchorX2, anchorY2, steps){
  for (let k = 0 ; k <= steps; k++) {
    
    t = k / steps;
    
    x = quadLerp(anchorX1, controlX, anchorX2, t);
    y = quadLerp(anchorY1, controlY, anchorY2, t);
      
    circle(x, y, 3);
  }
}

function quadLerp(p0, p1, p2, t){
  return lerp(lerp(p0, p1, t),
              lerp(p1, p2, t),
              t);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

您可以移动鼠标来控制内半径。 如果按住鼠标,您可以看到 anchor/control 个点。

同样可以绘制为当前和下一个内部点之间的四边形贝塞尔点作为锚点,当前外部点也作为锚点。