Pull to refresh

Почему это красиво? Странный эксперимент со спиралью Фибоначчи

Level of difficultyEasy
Reading time5 min
Views7.5K
Кот Фибоначчи
Кот Фибоначчи

Недавно делал небольшой скрипт для браузера, который может рисовать спираль Фибоначчи поверх фотографий на вебстранице. Все это делалось для того, чтобы проверить свою догадку, по поводу форм встречающихся в природе - вписываются ли они в спираль или нет. Рисовать я ее хотел не поверх котов, а поверх фото, которые немного даже поинтереснее будут, и поэтому и пишу такое длинное предисловие, потому что в отличие от поста в личном блоге, здесь аудитория может отнестись к таком не сильно положительно. Но с другой стороны, все мы люди, поэтому я рискну рассказать об этом эксперименте тут на хабре

В фотографии часто используется правило третей и золотое сечение - фраза буквально из статьи которая выдается первой по поиску в Яндексе, при запросе золотое сечение в фотографии. Вообще есть подозрение, что не все фото делаются специально по этому правилу, но итоге почти все "красивые" фото оказываются соответствующими такому правилу, и человек просто любуется таким фото, не подозревая что любуется математикой. Мало того, любуясь фотографиями девушек, я обратил внимание, что и там часто присутствует такое же золотое сечение - женские формы и изгибы тела очень часто хорошо вписываются в золотую спираль. Смотрите сами:

 Как видно изгиб тела практически идеально повторяет форму золотой спирали.
Как видно изгиб тела практически идеально повторяет форму золотой спирали.

Для того чтобы проверять в онлайн режиме соответствие форм на форму спирали Фибоначчи, я придумал сделать букмарклет, который бы рисовал прозрачный canvas поверх фотографий, а на нем рисовалась бы сама спираль, размер которой можно было бы изменять так чтобы она вписывалась куда надо. На мое счастье при поиске генератора спирали Фибоначчи на javascript, я сходу нашел решение в виде ответа на stackoverflow.com, где в конце автор ответа дал ссылку на JSFiddle, где можно погонять спираль и понять как она работает. Важно было сделать чтобы спираль рисовалась не только из центра, а ткуда потребуется, и это решилось вставкой простой конструкции:

canvas.addEventListener("mousedown", function (e) {
    center.x = e.clientX;
    center.y = e.clientY;
}, false);

Для того чтобы спираль рисовалась поверх открытой вебстраницы, нужно добавить элемент canvas, который я сделал по размеру всего окна, и который скролится вместе с вебстраницей (свойство css position:sticky) и рисуется поверх других элементов (z-index:2000):

let el = document.getElementsByTagName("body")[0];
let canvas = document.createElement("canvas");
canvas.id = "can";
el.insertBefore(canvas, el.firstChild);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.zIndex = "2000";
canvas.style.position = "sticky";
canvas.style.top = "0";
ctx = canvas.getContext("2d");

А для того, чтобы сделать выход из режима рисования спирали (по кнопке ESC), нужно добавить в код такую конструкцию:

  document.addEventListener("keydown", function(){
        var x=event.key || event.code;
        if(x=="Escape"){
        	canvas.remove();
        }
    })  

Потом весь код копируется и минимизируется, например при помощи сервиса https://jsminify.org

Полный код
let el = document.getElementsByTagName("body")[0];
let canvas = document.createElement("canvas");
canvas.id = "can";
el.insertBefore(canvas, el.firstChild);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.zIndex = "2000";
canvas.style.position = "sticky";
canvas.style.top = "0";
ctx = canvas.getContext("2d");

// Assume ctx is canvas 2D Context and ready to render to
var width = ctx.canvas.width;
var height = ctx.canvas.height;

var center = {
    x: width / 2,
    y: height / 2
};

canvas.addEventListener("mousedown", function (e) {
    center.x = e.clientX;
    center.y = e.clientY;
}, false);

document.addEventListener("keydown", function(){
        var x=event.key || event.code;
        if(x=="Escape"){
        	canvas.remove();
        }
    })  
canvas.addEventListener('mousemove', function(){
    var mouse = {};

    mouse.x = event.offsetX;
    mouse.y = event.offsetY;
    if(mouse.x === undefined){ // if firefox
        mouse.x = event.clientX;
        mouse.y = event.clientY;
    }

    // Substract offset (so it's centered at 0,0)
    mouse.x -= center.x;
    mouse.y -= center.y;

    drawFibonacciSpiral({x:0, y:0}, mouse);
});

var drawFibonacciSpiral = function(p1, p2){
    ctx.clearRect(0, 0, width, height);

    // Draw coord axis -> center viewport at 0,0
    drawStroke([{x:0, y:-center.y}, {x:0, y:center.y}], center, "gray");
    drawStroke([{x:-center.x, y:0}, {x:center.x, y:0}], center,"gray");

    // Draw spiral -> center viewport at 0,0
    drawStroke(getSpiral(p1, p2, getDistance({x:0,y:0},center)), center);
};

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

var drawStroke = function(points, offset, strokeColor){
    // Default value
    offset = offset || {x:0,y:0}; // Offset to center on screen
    strokeColor = strokeColor || "black";

    ctx.strokeStyle = strokeColor;
    ctx.beginPath();
    var p = points[0];
    ctx.moveTo(offset.x + p.x, offset.y + p.y);
    for(var i = 1; i < points.length; i++){
        p = points[i];
        ctx.lineTo(offset.x + p.x, offset.y + p.y);
    }
    ctx.stroke();  // draw it all
};

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 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, p;

    // Start at the center
    i = step = radius = angle = 0;

    // Continue drawing until reaching maximum radius
    while (radius * scale <= maxRadius){
        p = {
            x: scale * radius * Math.cos(angle + angleOffset) + pA.x,
            y: scale * radius * Math.sin(angle + angleOffset) + pA.y
        };

        path.push(p);

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

Минимизированный код теперь можно поместить в букмарклет, который можно сгенерировать где-нибудь в интернете (вообще была ссылка на блог, но удалил чтобы не посчитали рекламой). А можно воспользоваться уже готовой ссылкой: fiboCheck. Эту ссылку можно добавить на панель ссылок и если увидел фото кота или красивой девушки, то теперь всегда можешь проверить ее фигуру на золотое сечение, просто щелкнув по ссылке. 

Букмарклет свою работу в принципе выполняет, но есть несколько нареканий: например, нельзя переворачивать спирать, иногда так было бы удобнее, плюс еще заметил что размер спирали ограничивается, если рисовать ее в левой части канваса. Еще обратил внимание что и сама спираль генерируется немного с переломами.

В общем-то на этом и все о чем хотелось бы рассказать. Сильно не пинайте) В конце вот еще немного красивого:

Tags:
Hubs:
+19
Comments30

Articles