问题描述
我正在使用以下公式来计算二次曲线上的点:
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,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 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,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>
您可以移动鼠标来控制内半径。 如果按住鼠标键,可以看到锚点/控制点。
可以将当前和下一个内部点之间的四倍贝塞尔曲线点作为锚点,同时将当前外部点作为锚点来绘制相同的结果。