使用处理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并遍历曲线时,我得到了:

points on the quad curve

看起来好像是在曲线上(花朵形状)获取点,而且在“控制点”上也获取了所有点。

我用这个公式来产生分数:

    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,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,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 curves rendered between points of a 7 pointed star as circles interpolated between points

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

另一个建议是将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);

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

Illustration of quadratic Bézier curves in string art. In each case,end points marked with black circles and the control point marked with an X define the quadratic Bézier curve shown as a dotted line.

弦艺术中二次贝塞尔曲线的插图。在每种情况下,标有黑色圆圈的端点和标有X的控制点都定义了二次贝塞尔曲线,如Wikipedia user Cmglee

所示。

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

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

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

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

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

发生了很多事情,所以让我们尝试将其分解。

首先绘制星星和背后的数学符号:这与islia的问题类似,您可以看到我的detailed answer here

请注意她的问题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,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,204);

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

function flower(x,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,9);
      circle(xOuter,9);
    }
  }
}

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

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

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

bezier points

bezier points with visualised anchor and control points

可以将当前和下一个内部点之间的四倍贝塞尔曲线点作为锚点,同时将当前外部点作为锚点来绘制相同的结果。