HTML5 Canvas Fibonacci螺旋

目前我正在看这个代码,但无法弄清楚出了什么问题。

function fibNumbers() { return [0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] } function continiusFib(a) { var b = fibNumbers(), c = Math.floor(a), d = Math.ceil(a); if (d >= b.length) return null; a = Math.pow(a - c, 1.15); return b[c] + (b[d] - b[c]) * a } function drawSpiral(pointA, pointB) { var b = pointA; var c = pointB; ctx.translate(bx, by); b = Math.sqrt(((cx - bx) * (cx - bx)) + ((cy - by) * (cy - by))); d = 1 / Math.sqrt(((cx - bx) * (cx - bx)) + ((cy - by) * (cy - by))); c = Math.acos(cx - bx); 0 > Math.asin(cy - by) && (c = 2 * Math.PI - c); ctx.rotate(c); ctx.scale(b / 5, b / 5); var d = Math.PI / 100; ctx.moveTo(0, 0); for (var e = 0; e < 50 * (fibNumbers().length - 1) ; e++) { var f = e * d, g = continiusFib(e / 50), h = Math.cos(f) * g, f = Math.sin(f) * g; ctx.lineTo(h, f); } ctx.scale(5 / b, 5 / b); ctx.rotate(-c); //ctx.stroke(); } 

我想要的是绘制与Golden Spiral不同的Fibonacci Spiral

我也有这个问题供其他参考。 在此处输入图像描述

在此处输入图像描述

在你的函数drawSpiral ,在第四行中你做了:

 b = Math.sqrt(((cx - bx) * (cx - bx)) + ((cy - by) * (cy - by))); 

所以, b现在应该是一个标量,但是你试着访问bx并在下一行中,它不再存在:

 d = 1 / Math.sqrt(((cx - bx) * (cx - bx)) + ((cy - by) * (cy - by))); 

在第6-7行中c再次发生这种情况。 这可能是您的代码无法正常工作的原因。


我尝试使用自己的代码。 我完全不确定数学,但是我使用@ Blindman67的答案中的一些鼠标跟踪代码将我的算法基于你在问题上发布的片段。

螺旋

这是重要的部分。 它返回一个带有螺旋点的数组(我使用另一个函数实际渲染它们)。 我们的想法是使用您提供的连续斐波纳契函数绘制螺旋线。 它从A点开始并强制缩放,使得一圈的半径是A点和B点之间的距离。它还增加了角度偏移,因此一圈的角度是A点和B点之间的角度。

编辑解决注释:我将for循环更改for while循环,继续绘制直到螺旋达到最大半径。 我还更改了一些名称并添加了注释以尝试使算法更清晰。

 var getSpiral = function(pA, pB, maxRadius){ // 1 step = 1/4 turn or 90º var precision = 50; // Lines to draw in each 1/4 turn var stepB = 4; // Steps to get to point B var angleToPointB = getAngle(pA,pB); // Angle between pA and pB var distToPointB = getDistance(pA,pB); // Distance between pA and pB var fibonacci = new FibonacciGenerator(); // Find scale so that the last point of the curve is at distance to pB var radiusB = fibonacci.getNumber(stepB); var scale = distToPointB / radiusB; // Find angle offset so that last point of the curve is at angle to pB var angleOffset = angleToPointB - stepB * Math.PI / 2; var path = []; var i, step , radius, angle; // Start at the center i = step = radius = angle = 0; // Continue drawing until reaching maximum radius while (radius * scale <= maxRadius){ path.push({ x: scale * radius * Math.cos(angle + angleOffset) + pA.x, y: scale * radius * Math.sin(angle + angleOffset) + pA.y }); i++; // Next point step = i / precision; // 1/4 turns at point radius = fibonacci.getNumber(step); // Radius of Fibonacci spiral angle = step * Math.PI / 2; // Radians at point } return path; }; 

斐波那契序列

生成连续斐波那契数的代码基本上是你的,但我改了一些名字来帮助我理解它。 我还添加了一个生成器函数,因此它可以处理任何数字:

 var FibonacciGenerator = function(){ var thisFibonacci = this; // Start with 0 1 2... instead of the real sequence 0 1 1 2... thisFibonacci.array = [0, 1, 2]; thisFibonacci.getDiscrete = function(n){ // If the Fibonacci number is not in the array, calculate it while (n >= thisFibonacci.array.length){ var length = thisFibonacci.array.length; var nextFibonacci = thisFibonacci.array[length - 1] + thisFibonacci.array[length - 2]; thisFibonacci.array.push(nextFibonacci); } return thisFibonacci.array[n]; }; thisFibonacci.getNumber = function(n){ var floor = Math.floor(n); var ceil = Math.ceil(n); if (Math.floor(n) == n){ return thisFibonacci.getDiscrete(n); } var a = Math.pow(n - floor, 1.15); var fibFloor = thisFibonacci.getDiscrete(floor); var fibCeil = thisFibonacci.getDiscrete(ceil); return fibFloor + a * (fibCeil - fibFloor); }; return thisFibonacci; }; 

点之间的距离和角度

为了使代码更清晰,我使用了几个辅助函数来处理2D点:

 var getDistance = function(p1, p2){ return Math.sqrt(Math.pow(p1.x-p2.x, 2) + Math.pow(p1.y-p2.y, 2)); }; var getAngle = function(p1, p2){ return Math.atan2(p2.y-p1.y, p2.x-p1.x); }; 

整件事: JSFiddle和Updated-to-address-comment JSFiddle

这就是我做到的。 要做的是找到从点A到B的角度的螺旋半径,然后缩放螺旋以适应。

该函数将canvas上的螺旋渲染为以pointA为中心并与pointB相交。 它使用ctx.setTransform来定位螺旋以适应约束,或者你可以使用比例和中心偏移来转换siral点并保持默认的canvas转换(包括你正在绘制其他东西);

注意事项

  • 如果没有解决方案,则不绘制pointB === pointA。
  • 如果pointA远远超出canvas(我还没有测试过),可能无法绘制。
  • 总是从中心抽出来。 除了停在哪里之外,不考虑剪切螺旋。

所以对代码。 (更新)

 // Assume ctx is canvas 2D Context and ready to render to var cx = ctx.canvas.width / 2; var cy = ctx.canvas.height / 2; var font = "Verdana"; // font for annotation var fontSize = 12; // font size for annotation var angleWind = 0; var lastAng; function getScale(){ // gets the current transform scale // assumes transform is square. ie Y and X scale are equal and at right angles var a = ctx.currentTransform.a; // get x vector from current trans var b = ctx.currentTransform.b; return Math.sqrt(a * a + b * b); // work out the scale } // Code is just a quicky to annotate line and aid visualising current problem // Not meant for anything but this example. Only Tested on Chrome // This is needed as the canvas text API can not handle text at very small scales // so need to draw at unit scale over existing transformation function annotateLine(pA, pB, text, colour, where){ var scale, size, ang, xdx, xdy, len, textStart, ox, oy; scale = getScale(); // get the current scale size = fontSize; // get font size // use scale to create new origin at start of line ox = ctx.currentTransform.e + pA.x * scale ; oy = ctx.currentTransform.f + pA.y * scale; // get direction of the line ang = Math.atan2(pB.y - pA.y, pB.x - pA.x); xdx = Math.cos(ang); // get the new x vector for transform xdy = Math.sin(ang); // get the length of the new line to do annotation positioning len = Math.sqrt( Math.pow(pB.y - pA.y, 2) + Math.pow(pB.x - pA.x, 2) ) * scale; ctx.save(); // save current state //Set the unit scaled transform to render in ctx.setTransform(xdx, xdy, -xdy, xdx, ox, oy); // set fint ctx.font= size + "px " + font; // set start pos textStart = 0; where = where.toLowerCase(); // Because I can never get the cap right if(where.indexOf("start") > -1){ textStart = 0; // redundent I know but done }else if(where.indexOf("center") > -1 || where.indexOf("centre") > -1 ){ // both spellings // get the size of text and calculate where it should start to be centred textStart = (len - ctx.measureText(text).width) / 2; }else{ textStart = (len - ctx.measureText(text).width); } if(where.indexOf("below") > -1){ // check if below size = -size * 2; } // draw the text ctx.fillStyle = colour; ctx.fillText(text, textStart,-size / 2); ctx.restore(); // recall saved state } // Just draws a circle and should be self exlainatory function circle(pA, size, colour1, colour2){ size = size * 1 / getScale(); ctx.strokeStyle = colour1; ctx.fillStyle = colour2; ctx.beginPath(); ctx.arc(pA.x, pA.y, size , 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } function renderSpiral(pointA, pointB, turns){ var dx, dy, rad, i, ang, cx, cy, dist, a, c, angleStep, numberTurns, nTFPB, scale, styles, pA, pB; // clear the canvas ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // spiral stuff c = 1.358456; // constant See https://en.wikipedia.org/wiki/Golden_spiral angleStep = Math.PI/20; // set the angular resultion for drawing numberTurns = 6; // total half turns drawn nTFPB = 0; // numberOfTurnsForPointB is the number of turns to point // B should be integer and describes the number off // turns made befor reaching point B // get the ang from pointA to B ang = (Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) + Math.PI * 2) % (Math.PI *2 ); // Check for winding. If the angle crosses 2PI boundary from last call // then wind up or wind down the number of turns made to get to current // solution. if(lastAng !== undefined){ if(lastAng > Math.PI * 1.5 && ang < Math.PI * 0.5 ){ angleWind += 1; }else if(lastAng < Math.PI * 0.5 && ang > Math.PI * 1.5 ){ if(angleWind > 0){ angleWind -= 1; } } } lastAng = ang; // save last angle // Add the windings nTFPB += angleWind; // get the distance from A to B dist = Math.sqrt(Math.pow(pointB.y-pointA.y,2)+Math.pow((pointB.x)-pointA.x,2)); if(dist === 0){ return; // this makes no sense so exit as nothing to draw } // get the spiral radius at point B rad = Math.pow(c,ang + nTFPB * 2 * Math.PI); // spiral radius at point2 // now just need to get the correct scale so the spiral fist to the // constraints required. scale = dist / rad; while(Math.pow(c,Math.PI*numberTurns)*scale < ctx.canvas.width){ numberTurns += 2; } // set the scale, and origin to centre ctx.setTransform(scale, 0, 0, scale, pointA.x, pointA.y); // make it look nice create some line styles styles = [{ colour:"black", width:6 },{ colour:"gold", width:5 } ]; // Now draw the spiral. draw it for each style styles.forEach( function(style) { ctx.strokeStyle = style.colour; ctx.lineWidth = style.width * ( 1 / scale); // because it is scaled invert the scale // can calculate the width required // ready to draw ctx.beginPath(); for( i = 0; i <= Math.PI *numberTurns; i+= angleStep){ dx = Math.cos(i); // get the vector for angle i dy = Math.sin(i); var rad = Math.pow(c, i); // calculate the radius if(i === 0) { ctx.moveTo(dx * rad , dy * rad ); // start at center }else{ ctx.lineTo(dx * rad , dy * rad ); // add line } } ctx.stroke(); // draw it all }); // first just draw the line AB ctx.strokeStyle = "black"; ctx.lineWidth = 2 * ( 1 / scale); // because it is scaled invert the scale // can calculate the width required // some code to help me work this out. Having hard time visualising solution pA = {x: 0, y: 0}; pB = {x: 1, y: 0}; pB.x = ( pointB.x - pointA.x ) * ( 1 / scale ); pB.y = ( pointB.y - pointA.y ) * ( 1 / scale ); // ready to draw ctx.beginPath(); ctx.moveTo( pA.x, pA.y ); // start at center ctx.lineTo( pB.x, pB.y ); // add line ctx.stroke(); // draw it all if(scale > 10){ ctx.strokeStyle = "blue"; ctx.lineWidth = 1 * ( 1 / scale); ctx.beginPath(); ctx.moveTo( 0, 0 ); // start at center ctx.lineTo( 1, 0 ); // add line ctx.stroke(); // draw it all } annotateLine(pA, pB, "" + ((ang + angleWind * Math.PI * 2) / Math.PI).toFixed(2) + "π", "black", "centre"); annotateLine(pA, pB, "" + rad.toFixed(2), "black", "centre below"); if(scale > 10){ annotateLine({x: 0, y: 0}, {x: 1, y: 0}, "1 Unit", "blue", "centre"); } circle(pA, 5, "black", "white"); circle(pB, 5, "black", "white"); ctx.setTransform(1,0,0,1,0,0); // reset transform to default; } var centerMove = 0; canvasMouseCallBack = function(){ centerMove += 0.0; renderSpiral( { x:cx+Math.sin(centerMove)*100, y:cy+Math.cos(centerMove)*100 }, {x:mouse.x,y:mouse.y} ); }; 

希望这可以帮助。 对于额外的水果感到抱歉,但我必须测试它,所以我只是将它全部复制为答案。

我为那些想要看它运行的人添加了一个小提琴。 PointA被自动移动(因此在移动鼠标时看起来有点奇怪),因为我无法添加适当的界面。

更新:我已经更新了答案并试图找到更新问题的更好解决方案。 不幸的是,我无法满足新的要求,但从我的分析中我发现这些要求是一个无法解决的问题。 也就是说,当螺旋角接近零时,尺度(在解中)接近无穷大,渐近线在PI / 4附近,但因为这只是近似,所以它都变得毫无意义。 A点和B点有一组位置,无法安装螺旋线。 这是我的解释,并不代表没有解决方案,因为我没有提供证据。

小提琴(更新)