Спиральный рисунок Фибоначчи в холсте HTML5
В настоящее время я просматриваю этот код, но не могу понять, что случилось.
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(b.x, b.y);
b = Math.sqrt(((c.x - b.x) * (c.x - b.x)) + ((c.y - b.y) * (c.y - b.y)));
d = 1 / Math.sqrt(((c.x - b.x) * (c.x - b.x)) + ((c.y - b.y) * (c.y - b.y)));
c = Math.acos(c.x - b.x);
0 > Math.asin(c.y - b.y) && (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();
}
Я хочу, чтобы нарисовать спину Фибоначчи, которая отличается от Золотой Спирали.
У меня также есть question для других ссылок.
![введите описание изображения здесь]()
![введите описание изображения здесь]()
Ответы
Ответ 1
В вашей функции drawSpiral
в четвертой строке вы выполните:
b = Math.sqrt(((c.x - b.x) * (c.x - b.x)) + ((c.y - b.y) * (c.y - b.y)));
Итак, b
теперь должен быть скаляром, но затем вы пытаетесь получить доступ к b.x
и b.y
в следующей строке, которые больше не существуют:
d = 1 / Math.sqrt(((c.x - b.x) * (c.x - b.x)) + ((c.y - b.y) * (c.y - b.y)));
Это происходит снова с c
в 6-7 строках. Возможно, поэтому ваш код не работает.
Я попытался заставить его работать с моим собственным кодом. Я не совсем уверен в математике, но я основывал свой алгоритм на фрагменте, который вы отправили на вопрос, используя некоторый код отслеживания мыши из ответа @Blindman67.
Спираль
Это важная часть. Он возвращает массив со спиральными точками (я использую другую функцию для рендеринга). Идея состоит в том, чтобы нарисовать спираль, используя функцию непрерывного фибоначчи, которую вы предоставили. Он начинается в точке A и заставляет масштабирование так, чтобы радиус на одном повороте был расстоянием между точкой A и точкой B. Он также добавляет смещение угла, поэтому угол на одном повороте - это угол между точками A и B.
Отредактировано для комментария к адресу: Я изменил цикл 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;
};
Расстояние и угол между точками
Чтобы сделать код более понятным, я использовал пару вспомогательных функций для работы с двумерными точками:
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 и Обновлено до адреса -комментарий JSFiddle
Ответ 2
Вот как я это сделал. Дело в том, чтобы найти радиус спирали под углом от точки А до В, а затем масштабировать спираль, чтобы она соответствовала.
Функция отображает спираль на холсте с центром в точке А и пересекающей точку Б. Он использует ctx.setTransform
, чтобы поместить спираль в соответствии с ограничениями, или вы можете просто использовать смещения шкалы и центра, чтобы преобразовать точки с сиротом и сохранить преобразование холста по умолчанию (если вы рисуете другие вещи);
Предостережения
- Не рисовать, если pointB === pointA, поскольку нет решения.
- Нельзя рисовать, если pointA находится далеко за пределами холста (у меня нет
испытал это).
- Всегда вытягивается из центра. Не рассматривает отсечение
спираль, отличная от остановки.
Так к коду. (Обновлено)
// 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 A-B
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 be перемещается автоматически (так выглядит немного странно, когда вы перемещаете мышь), поскольку меня не беспокоит добавление правильного интерфейса.
UPDATE:
Я обновил ответ и попытался найти лучшее решение для обновленного вопроса. К сожалению, я не смог соответствовать новым требованиям, хотя из моего анализа я обнаружил, что требования представляют неразрешимую проблему. А именно, когда спиральный угол приближается к нулю, масштаб (в растворе) приближается к бесконечности, асимптота находится где-то вблизи PI/4, но поскольку это всего лишь приближение, все становится бессмысленным. Существует множество мест для точек A и B, где спираль не может быть установлена. Это моя интерпретация и не означает, что нет решения, поскольку я не представил доказательства.
Fiddle (обновлено)