4

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

  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);    
}
}
4

1 回答 1

7

多么可爱的问题。

突出的一件事是这部分:

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);where 就像在任何其他情况下一样,您推送 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>

在 7 个尖角星的点之间呈现二次曲线,作为在点之间插值的圆

我个人建议养成格式化代码的习惯:它使阅读代码和发现问题更容易。您编写的程序越多,程序越大,您花在阅读代码上的时间就越多,因此使代码可读肯定会得到回报。

另一个建议是将二次贝塞尔公式封装在一个函数中:

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 标记的控制点定义了二次贝塞尔曲线,如虚线所示。

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

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

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

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

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

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

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

从绘制星星和它背后的数学开始:这类似于 islia 的问题,您可以在此处查看我的详细答案

请注意她问题中的明星示例:这不是一个糟糕的起点,因为我们不必担心二次贝塞尔点。它确实介绍了您可能还不熟悉的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>

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

贝塞尔点

具有可视化锚点和控制点的贝塞尔点

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

于 2020-08-21T23:19:19.660 回答