19 мая в 05:10

Эволюция фрактальных монстров

Сегодня будем рисовать геометрические фракталы, которым уделяют незаслуженно мало внимания. А между тем, тут каждый фрактал — маленький шедевр, поражающий воображение!


Дальше много картинок и gif-анимация. Но прежде, чем переходить под кат, посмотрите на картинку выше и скажите, что на ней нарисовано?

Алгоритм


Рассмотрим один любопытный алгоритм на примере известного фрактала — кривой Леви.
Есть у нас две точки A1 и A2. Найдем такую точку A3, для которой угол A1 = 45°, а угол A3 = 90°.

Проделаем ту же операцию для точек A1, A3 и A3, A2.

И дальше рекурсивно для каждой пары точек.

Третья итерация:

На 14 итерации эта кривая выглядит вот так:

Более наглядно на JavaScript:

Исходник на JS
  <canvas id="myCanvas"></canvas>
  <script>
    window.onload=function(){
      var canvas=document.getElementById('myCanvas');
      var context=canvas.getContext('2d');
      canvas.width=800, canvas.height=800;
      context.beginPath();
      recurs(context, 17, 400, 250, 400, 550); //рекурсивная функция, рисующая фрактальную кривую
      context.stroke();
    };
    var angle=45*Math.PI/180; //переводим углы в радианы
    function recurs(context, n, x0, y0, x1, y1){ //n итераций, Точки A1(x0,y0) и A2(x1,y1)
      if (n==0){
        context.moveTo(x0,y0);
        context.lineTo(x1,y1);
      }else{
        var xx=Math.cos(angle)*((x1-x0)*Math.cos(angle)-(y1-y0)*Math.sin(angle))+x0;
        var yy=Math.cos(angle)*((x1-x0)*Math.sin(angle)+(y1-y0)*Math.cos(angle))+y0; //находим точку A3
        recurs(context, n-1, x0, y0, xx, yy); //A1, A3
        recurs(context, n-1, xx, yy, x1, y1); //A3, A2
      }
    }
  </script>

Запустить в браузере

Немного модифицировав приведенный выше код, можем нарисовать другую известную фрактальную кривую — Дракон Хартера-Хейтуэя. Для этого, начиная со второй итерации, будем чередовать углы 45° и -45°

Третья итерация:

На 14 итерации эта кривая выглядит вот так:


Модифицированный фрагмент исходника
      }else{
        angle2=angle*k; // <-
        var xx=Math.cos(angle2)*((x1-x0)*Math.cos(angle2)-(y1-y0)*Math.sin(angle2))+x0;
        var yy=Math.cos(angle2)*((x1-x0)*Math.sin(angle2)+(y1-y0)*Math.cos(angle2))+y0;
        recurs(context, n-1, x0, y0, xx, yy, 1); //последний аргумент функции - k
        recurs(context, n-1, xx, yy, x1, y1, -1);
      }

Запустить в браузере

Мы самую малость изменили нашу функцию, добавив в нее еще один угол, а фрактал изменился до неузнаваемости! В динамике (изменяем второй угол с шагом в 5°):

И вот тут наш пытливый ум начинает задавать вопросы. А что, если попробовать использовать другие значения углов, вместо [45,-45]? Скажем, [30,-60]. Или [30,-30,-60,60]. Перепишем нашу функцию, чтобы она принимала массив с углами, в качестве одного из аргументов.

Окончательный вариант функции
    function recurs(context, arr, position, n, x0, y0, x1, y1){
      if (n==0){
        context.fillRect(x1,y1, 1,1);
        return position;
      }else{
        if (!position[n]) position[n]=0;
        var xx=Math.cos(arr[position[n]])*((x1-x0)*Math.cos(arr[position[n]])-(y1-y0)*Math.sin(arr[position[n]]))+x0;
        var yy=Math.cos(arr[position[n]])*((x1-x0)*Math.sin(arr[position[n]])+(y1-y0)*Math.cos(arr[position[n]]))+y0;
        position[n]++;
        if (position[n]==arr.length) position[n]=0;
        position=recurs(context, arr, position, n-1, x0, y0, xx, yy);
        position=recurs(context, arr, position, n-1, xx, yy, x1, y1);
        return position;
      }
    }

Массив position хранит номер угла из массива arr для текущей глубины рекурсии (n).

Запустить в браузере (Через запятую пишем значения углов в градусах, жмем «draw!»)

С этого момента будем рисовать только точки, не соединяя их линиями… чисто из эстетических соображений. Фрактал [30,-30,-60,60] нарисованный линиями и точками:



Особое внимание следует обратить на то, что все точки A3 лежат на окружности с диаметром A1A2:

Если точки находятся внутри (или снаружи) окружности — фрактал получается слишком сжатым (или наоборот — выходит за пределы области отрисовки). Наглядно:



Вот этим косинусом мы выравниваем точки по окружности:
var xx=Math.cos(arr[position[n]])* ...
var yy=Math.cos(arr[position[n]])* ...

Не трудно заметить, что используя угол 120° мы получим ту же точку, что и с углом -60°. Поэтому все углы лежат в диапазоне (-90°,90°).

Попробуем ввести в наш скрипт разные комбинации углов.

«Маленькие шедевры»


Несколько рандомных фракталов с углами -45°/45° (кликабельно):



С углами -30°/30°/-60°/60°:



С углами -15°/15°/-30°/30°/-45°/45°/-60°/60°/-75/75°:



… Так можно продолжать очень долго. Если использовать углы 15°/30°/45°/60°/75° (положительные и отрицательные) и, скажем, рисовать «восьмиугольные» фракталы — сколько всего фракталов мы сможем нарисовать? Примерно 10^8 = 100 000 000 штук. Кроме того, множество «восьмиугольных» фракталов не имеет пересечений с множеством, скажем, «девятиугольных» фракталов (за исключением фракталов, у которых все углы одинаковые). В общем, очень немало фракталов можно нарисовать этим алгоритмом.

Искать вручную что-то среди этого множества, при этом не зная, как это «что-то» должно выглядеть — немного затруднительно. Что мешает нам прикрутить сюда генетический алгоритм? Ничего. Прикрутим!

Эволюция


Генетический алгоритм — эвристический алгоритм поиска, использующий механизмы биологической эволюции. Алгоритм прост, интуитивно понятен и вместе с тем довольно эффективен.

Делится на несколько этапов.

  • Создается начальная популяций, состоящая из небольшого числа особей. Каждая особь представляет из себя массив фиксированной длины с набором генов (генотип). Генотипы начальной популяции заполняются случайным образом. Применительно к нашей задаче, гены — это углы, с помощью которых будем рисовать фракталы.

  • На следующем этапе происходит селекция. Для каждой особи считаем коэффициент приспособленности (fitness). Чем лучше особь — тем выше коэффициент. Сортируем популяцию по этому коэффициенту. Делим популяцию пополам — из наиболее приспособленных будем формировать следующую популяцию. (Как делить и что выбирать для следующей популяции — тема, на которую можно долго спорить).

  • Следующий этап — скрещивание. Из наиболее приспособленных выбираем (случайным образом) две особи — родителей. Делаем двух потомков: берем случайным образом часть генов одного родителя и часть генов другого, заполняем ими генотип одного из потомков. Оставшиеся гены уходят второму потомку. Берем следующих двух родителей. Проделываем ту же операцию. Так пока не закончатся родители. Из родителей и потомков формируем новую популяцию.

  • Заключительный этап — мутации. Берем некоторый процент особей из новой популяции, для каждой из них (особей) выбираем случайным образом ген и заменяем его на случайное значение. Таким образом мы добавляем в наш генофонд гены, которых не было в начальной популяции и которые, в перспективе, могут сформировать наилучшие результаты. Кроме того, повышая процент мутаций, можно в какой-то степени решить проблему сходимости к локальному оптимуму.

Каждый этап более наглядно на JavaScript.

Начальная популяция

Генерируем углы
function randomangl(min, max, step){
  if(step==0) step=1;
  var rand=Math.floor(Math.random() * (max/step - min/step + 1)) + min/step;
  if(rand==0) rand=1;
  rand*=step;
  if(rand>max) rand=max;
  return Math.round(rand);
}

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

randomangl(-45, 45, 90) — сгенерирует углы -45°/45°
randomangl(-60, 60, 30) — сгенерирует углы -60°/-30°/30°/60°
randomangl(-75, 75, 15) — сгенерирует углы -75°/-60°/-45°/-30°/-15°/15°/30°/45°/60°/75°

Углы 0° не используем. Фрактал [45°,0°] выглядел бы вот так:

Получаем тот же фрактал [45°], только в том месте, где пытаемся нарисовать 0° — точки сливаются (вся дальнейшая рекурсия в этом месте становится бессмысленной). По этой же причине, нет смысла генерировать фракталы с использованием 90°. Для начальной популяции лучше всего вызвать функцию randomangl(-75, 75, 15). Для мутаций — randomangl(-89, 89, 1).

Создаем начальную популяцию
population=[];
fitness=[];
for(var n=anglemin; n<=anglemax; n++){
  population[n]=[];
  fitness[n]=[];
  for(var i=0; i<PopulationSize; i++){
    population[n][i]=[];
    fitness[n][i]=0;
    for(var j=0; j<n; j++){
      population[n][i][j]=randomangl(min, max, step);
      if(j==0) population[n][i][j]=Math.abs(population[n][i][j]);
    }
  }
}

Создаем сразу несколько популяций с разным количеством углов (anglemin, anglemax). Инициализируем отдельный массив с коэффициентам приспособленности. Первый ген у каждой особи принудительно делаем положительным — фракталы [45°,-45°] и [-45°,45°] симметричны.

Селекция

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

Есть два варианта. Можно было бы нарисовать все фракталы из популяций и предложить пользователю отобрать самые лучшие, но могут возникнуть некоторые трудности — как выбрать лучшие фракталы, когда они все выглядят впечатляюще? Намного проще сравнивать два случайных фрактала.

Интерфейс:



На страничке два канваса: myCanvas1 и myCanvas2. И две кнопочки: onclick=«selecter(1);» и onclick=«selecter(2);»

Вызываем функцию, которая рисует два случайных фрактала:

Рисуем два случайных фрактала
function get2fractals(){
  PopulationNumber=Math.floor(Math.random() * (anglemax - anglemin + 1))+anglemin;
  drawcanvas(1);
  drawcanvas(2);
}

function drawcanvas(n){
  var canvas=document.getElementById('myCanvas'+n);
  var context=canvas.getContext('2d');
  canvas.width=canvasSize, canvas.height=canvasSize;
  context.fillStyle = 'rgb(255,255,255)';
  context.fillRect (0, 0, canvas.width, canvas.height);
  var position=[];
  
  if(n==1){
    FractalNumber[1]=Math.floor(Math.random() * (PopulationSize));
  }else{
    do{
      var rnd = Math.floor(Math.random() * (PopulationSize));
    }while(rnd==FractalNumber[1])
    FractalNumber[2]=rnd;
  }
  
  var array1fractal=population[PopulationNumber][FractalNumber[n]];

  context.fillStyle = 'rgb(0,0,0)';
  draw(context, array1fractal, position, iteration, xA, yA, xB, yB);
}

В функции get2fractals выбираем случайную популяцию.
В функции drawcanvas проверяем if(n==1) — чтобы не рисовать два одинаковых фрактала. Номер популяции и номера двух фракталов храним в глобальных переменных (PopulationNumber и FractalNumber[]).

Когда пользователь жмет на кнопку Select рядом с понравившимся ему фракталом, вызываем такую функцию:

Select
function selecter(n){
	fitness[PopulationNumber][FractalNumber[n]]++;
	get2fractals();
}

Которая повышает коэффициент приспособленности для выбранного фрактала и рисует два новых фрактала.

Будем считать, что пользователь оказался добросовестным и произвел выборов не меньше, чем количество особей в популяции, прежде чем запустить механизм эволюции.

Скрещивание и мутации

Скрещивания и мутации
function sortf(a, b) {
  if (a[1] < b[1]) return 1;
  else if (a[1] > b[1]) return -1;
  else return 0;
}

function evolution(){
  var mutation=document.getElementById("mutatepercent").value;
  var exchange=document.getElementById("exchangepercent").checked;
  
  var sizehalf=PopulationSize/2;
  var sizequarter=sizehalf/2;
  for(var n=anglemin; n<=anglemax; n++){
  
    var arrayt=[];
    for(var i=0; i<PopulationSize; i++){
      arrayt[i]=[];
      arrayt[i][0]=population[n][i];
      arrayt[i][1]=fitness[n][i];
    }
    arrayt.sort(sortf);
    arrayt.length=sizehalf;
    population[n].length=0;
    fitness[n].length=0;
    
    for(var i=0; i<sizequarter; i++){
      var i0=i*4;
      var i1=i*4+1;
      var i2=i*4+2;
      var i3=i*4+3;
      
      var removed1=Math.floor(Math.random()*(arrayt.length));
      var parent1f = arrayt.splice(removed1,1);
      var parent1=parent1f[0][0];
      var removed2=Math.floor(Math.random()*(arrayt.length));
      var parent2f = arrayt.splice(removed2,1);
      var parent2=parent2f[0][0];
      
      var child1=[];
      var child2=[];
      
      for(var j=0; j<n; j++){
        var gen=Math.round(Math.random());
        if(gen==1){
          child1[j]=parent1[j];
          child2[j]=parent2[j];
        }else{
          child1[j]=parent2[j];
          child2[j]=parent1[j];
        }
      }
      
      population[n][i0]=parent1;
      population[n][i1]=parent2;
      population[n][i2]=child1;
      population[n][i3]=child2;
        
      fitness[n][i0]=0;
      fitness[n][i1]=0;
      fitness[n][i2]=0;
      fitness[n][i3]=0;
    }

    if(mutation>0){
      var m=100/mutation;
      for(var i=0; i<PopulationSize; i++){
        var rnd=Math.floor(Math.random()*(m))+1;
        if(rnd==1){
          var mutatedgene=Math.floor(Math.random()*(n));
          population[n][i][mutatedgene]=randomangl(minm, maxm, stepm);
        }
      }
    }
  }

  if(exchange){
    ///...
  }
  get2fractals();
}

Берем поочередно каждую популяцию. Пишем всех особей во временный массив, туда же пишем их коэффициенты приспособленности fitness. Сортируем временный массив по этим коэффициентам. Отрезаем половинку (самых приспособленных). Старые два массива обнуляем, чтобы там ничего лишнего не осталось. Дергаем из временного массива двух предков, формируем двух потомков. Заполняем популяцию предками и потомками.

Производим мутации. Небольшое замечание. Вместо процента мутаций во всей популяции, используем вероятность мутации отдельно взятой особи.

Доступна опция exchange. С помощью этой опции реализована модель миграции особей между популяциями.

Модель миграции
if(exchange){
  var n=anglemin;
  while(n<population.length){
    var rnd=Math.round(Math.random());
    var np=n+1;
    if(rnd==1 && (typeof population[np]!="undefined")){
      
      var i1=Math.floor(Math.random() * (PopulationSize));
      var i2=Math.floor(Math.random() * (PopulationSize));
      var tempa1=population[np][i1];
      var tempa2=population[n][i2];
      var indexofexcessgene=Math.floor(Math.random()*(tempa1.length));
      var excessgene=tempa1.splice(indexofexcessgene,1);
      tempa2.splice(indexofexcessgene,0,excessgene[0]);
      population[np][i1]=tempa2;
      population[n][i2]=tempa1;
      
      n+=2;
    }else{
      n++;
    }
  }
}

Если флажок Exchanges установлен, поочередно для каждой популяции, одна случайная особь с вероятность 50% может обменяться генами со случайной особью из следующей популяции (популяция n+1). Генов в генотипах следующей популяции больше, обмен производим следующим образом. Выбираем случайный ген у особи в популяции n+1, вставляем его на ту же позицию в генотип у особи из популяции n. Меняем особи местами:


Осталось добавить свистелок (css-кнопочки, сохранение всего процесса в localStorage, отображение Parents Tree...).

Исходники можно посмотреть на GitHub
Или запустить в браузере, перейдя на сайт проекта (осторожно, можно залипнуть!)


Результат моих селекций — на картинке в начале публикации.

Ну и в заключение добавлю, что 2D — хорошо, а 3D — еще лучше!


Покрутить мышкой фрактал в трех измерениях можно здесь. Через слеш вбиваем пары углов /alpha/beta/alpa/beta/… в адресную строку.


Alpha — уже привычный нам угол поворота. Новый угол Beta — поворот точки A3 вокруг оси A1A2.

баг в 3D алгоритме
В 3D алгоритме есть забавный баг (фича), который не стал исправлять. Чтобы нарисовать Дракон Хартера-Хейтуэя, надо использовать 4 пары углов: 45/180/45/0/45/0/45/180.
3D алгоритмом можно нарисовать двумерные фракталы, которые не получится нарисовать 2D алгоритмом. Например:
45/0/45/180/45/180/45/180/45/0


45/180/45/180/45/0/45/180/45/180/


Небольшая галерея двумерных фракталов нарисованных этим алгоритмом.


Сергей Герасимов @xcont
карма
56,0
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (16)

  • +1
    Класс. Еще интересные фракталы получаются при вероятностном выборе афинных преобразований. Самый известный лист Папоротника. https://www.kv.by/archive/index2002491201.htm
    В целом же фракталы интересны тем, что с их помощью можно обьяснять теорему о неполноте Геделя, и проблему остановки Тьюринга. Ну и как пример математического Платонизма тоже.
    • 0
      фракталы интересны тем, что с их помощью можно обьяснять теорему о неполноте Геделя

      Интересно! А как?

  • +1
    Было время, баловался с таким, но это было до того, как я наткнулся на генетические алгоритмы — где-то в 2006м. С тех пор успел запустить собственные :)
  • +1

    Разбавьте текстурками и фантазией: fractal-orgy-is-the-first-ever-raw-fractal-to-be-banned + youtube


    Вот за что я люблю математичку! (с)

  • +1
    большой проект эволюционирующих фракталов, где пользователь является фактором естественного отбора — Electric Sheep.

    Если поискать, у них очень занятные генеалогические деревья есть.
  • +3
    Вот ещё предложение по улучшению трёхмерной визуализации: сглаженные пиксели и интенсивность, зависящая от глубины.
    <script></script>
    <script>
    var canvas;
    var canvasData;
    var context;
    var massiv;
    var angls=[[45,180],[-45,90],[-45,90],[45,180]];
    
    function createRequestObject() {
      if (typeof XMLHttpRequest === 'undefined') {
        XMLHttpRequest = function() {
          try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
            catch(e) {}
          try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
            catch(e) {}
          try { return new ActiveXObject("Msxml2.XMLHTTP"); }
            catch(e) {}
          try { return new ActiveXObject("Microsoft.XMLHTTP"); }
            catch(e) {}
          throw new Error("This browser does not support XMLHttpRequest.");
        };
      }
      return new XMLHttpRequest();
    }
    function selecter(num){
      req = new XMLHttpRequest();
      if (req) {
        req.open("POST", '/geom3d/', true);
        req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        req.onreadystatechange = processReqChange;
        var postmyplease="num="+num;
        req.send(postmyplease);
      }
    }
    function processReqChange(){
      try {
        if (req.readyState == 4) {
          if (req.status == 200) {
            var ang=JSON.parse(req.responseText);
            angls=ang.response;
            massiv=sfera(angls);
            droveLines(canvas, 0, 0, massiv);
            var logger=document.getElementById('logger');
            logger.innerHTML=" <br />";
            var link="";
            for (i=0, len=angls.length; i<len; i++) {
              //logger.innerHTML += "("+angls[i][0]+", "+angls[i][1]+") <br />";
              link+=angls[i][0]+"/"+angls[i][1]+"/";
            }
            logger.innerHTML="<a href=\"http://fractal.xcont.com/geom3d/"+link+"\">"+link+"</a>";
          } else {
              alert("Не удалось получить данные:\n" + req.statusText);
          }
        }
      }
      catch( e ) {}
    }
    
    function rotate(v, p, cos, sin, mcos){
      var x=v[0], y=v[1], z=v[2]; // вектор, вокруг которого вращаем
      var xp=p[0], yp=p[1], zp=p[2]; // точка, которую вращаем
      var a11=(mcos*x*x+cos), a12=(mcos*x*y-sin*z), a13=(mcos*x*z+sin*y); // матрица поворота вокруг вектора
      var a21=(mcos*x*y+sin*z), a22=(mcos*y*y+cos), a23=(mcos*z*y-sin*x);
      var a31=(mcos*x*z-sin*y), a32=(mcos*z*y+sin*x), a33=(mcos*z*z+cos);
      var xx=xp*a11+yp*a12+zp*a13; // умножаем точку на матрицу
      var yy=xp*a21+yp*a22+zp*a23;
      var zz=xp*a31+yp*a32+zp*a33;
      return [xx, yy, zz];
    }
    function find_point(a, b, c, angls){
      var alfa=angls[0]*Math.PI/180, betta=angls[1]*Math.PI/180;
      var x1=a[0], y1=a[1], z1=a[2]; //A
      var x2=b[0], y2=b[1]; z2=b[2]; //B - ось вращения AB
      var x3=c[0], y3=c[1], z3=c[2]; //C - третья точка нужна для задания плоскости
      var xa=x2-x1, ya=y2-y1, za=z2-z1; // вектор AB
      var xc=x3-x1, yc=y3-y1, zc=z3-z1; // вектор AC
      var xv=(ya*zc-za*yc), yv=-(xa*zc-za*xc), zv=(xa*yc-ya*xc); // векторное произведение векторов AB и AC (получаем вектор-перпиндикуляр к плоскости)
      var cosa=Math.cos(alfa); // всю тригонометрию из матрицы поворота считаем заранее (оптимизация)
      var sina=Math.sin(alfa);
      var mcosa=1-Math.cos(alfa);
      var cosb=Math.cos(betta);
      var sinb=Math.sin(betta);
      var mcosb=1-Math.cos(betta);
      var r=Math.sqrt(Math.pow(xv,2)+Math.pow(yv,2)+Math.pow(zv,2)); // длина вектора-перпиндикуляра
      var vector=[xv/r, yv/r, zv/r]; // нормированный вектор
      var point=[xa, ya, za]; // вокруг вектора-перпендикуляра делаем поворот точки A на угол alfa
      var d=rotate(vector, point, cosa, sina, mcosa); // точка после поворота
      r=Math.sqrt(Math.pow(xa,2)+Math.pow(ya,2)+Math.pow(za,2)); // длина вектора AB
      vector=[xa/r, ya/r, za/r]; // нормированный вектор
      point=[d[0]*Math.cos(alfa),d[1]*Math.cos(alfa),d[2]*Math.cos(alfa)]; // угол ADC=90°
      d=rotate(vector, point, cosb, sinb, mcosb); // вращаем точку D вокруг AB
      d=[d[0]+x1, d[1]+y1, d[2]+z1]; // сдвигаем точку D
      return d;
    }
    function recurs(array, n, a, b, d, angls){
      if (n==0){
        return array;
      }else{
        if (!array[0][n]) array[0][n]=0;
        var d=find_point(a, b, d, angls[array[0][n]]);
        array[1].push(d);
        array[0][n]++;
        if (array[0][n]==angls.length) array[0][n]=0;
        recurs(array, n-1, a, d, b, angls);
        recurs(array, n-1, d, b, a, angls);
        return array;
      }
    }
    function sfera(angls){
      var massiv=new Array();
      var x1=0; y1=10; z1=0;
      var x2=0; y2=-10; z2=0;
      var x3=0; y3=0; z3=-10;
      massiv[0]=[x1, y1, z1];
      massiv[1]=[x2, y2, z2];
      massiv[2]=[x3, y3, z3];
      var d=find_point(massiv[0], massiv[1], massiv[2], [angls[0][0], 0]);
      massiv[2]=d;
      var m1=new Array();
      var m2=new Array();
      var array=[m1,m2];
      d=recurs(array, 15, massiv[0], massiv[1], massiv[2], angls);
      massiv = massiv.concat(d[1]);
      return massiv;
    }
      
      
    window.onload=function(){
      canvas=document.getElementById('myCanvas');
      context=canvas.getContext('2d');
      canvas.width=600;
      canvas.height=600;
      canvasData=context.getImageData(0, 0, canvas.width, canvas.height);
      
      massiv=sfera(angls);
      droveLines(canvas, 0, 0, massiv);
      
      canvas.addEventListener('mousemove', function(evt){
          var mousePos=getMousePos(canvas, evt);
          droveLines(canvas, mousePos.x, mousePos.y, massiv);
        }, false);
        
      function getMousePos(canvas, evt){
        var obj=canvas;
        var top=0;
        var left=0;
        while (obj && obj.tagName != 'BODY') {
          top+=obj.offsetTop;
          left+=obj.offsetLeft;
          obj=obj.offsetParent;
        }
       
        var mouseX=evt.clientX-left+window.pageXOffset;
        var mouseY=evt.clientY-top+window.pageYOffset;
        return {
          x: mouseX,
          y: mouseY
        };
      }
    }
    
    function droveLines(canvas, xMouse, yMouse, massiv){
      clearCanvas();
      var xCenter=canvas.width/2;
      var yCenter=canvas.height/2;
      var focus=800;
      var i, x, y, z, xx, yy, zz;
      var arr=new Array();
      var arr2=new Array();
      var alfa=yMouse*Math.PI/180; 
      var betta=xMouse*Math.PI/180;
      var cosa=Math.cos(alfa);
      var cosb=Math.cos(betta);
      var sina=Math.sin(alfa);
      var sinb=Math.sin(betta);
      
      for (i=0, len=massiv.length; i<len; i++) {
        x=massiv[i][0];
        y=massiv[i][1];
        z=massiv[i][2];
        yy=y*cosa-z*sina;
        zz=z*cosa+y*sina;
        z=zz;
        xx=x*cosb+z*sinb;
        zz=z*cosb-x*sinb;
        y=yy; x=xx;  z=zz;
        z+=64;
        xx=focus*x/z+xCenter;
        yy=focus*y/z+yCenter;
    
        var fade = Math.max(Math.min(0.8 - z / 100.0, 1.0), 0.0);
        fade = (fade * fade + fade) * 0.5;
    
        drawPixel(xx, yy, 450 * fade, 370 * fade, 150 * fade);
      }
      updateCanvas();
    }
    
    function drawPixel(x, y, r, g, b) {
      var ix = Math.floor(x);
      var iy = Math.floor(y);
      var dx = x - ix;
      var dy = y - iy;
      var v00 = (1.0 - dx) * (1.0 - dy);
      var v10 = dx * (1.0 - dy);
      var v01 = (1.0 - dx) * dy;
      var v11 = dx * dy;
    
      var index = (ix + iy * canvas.width) * 4;
      var d = canvasData.data;
    
      index = Math.max(Math.min(index, d.length - canvas.width * 4 - 4), 0);
    
      d[index + 0] = Math.min(d[index + 0] + r * v00, 255);
      d[index + 1] = Math.min(d[index + 1] + g * v00, 255);
      d[index + 2] = Math.min(d[index + 2] + b * v00, 255);
      d[index + 3] = 255;
    
      d[index + 4] = Math.min(d[index + 4] + r * v10, 255);
      d[index + 5] = Math.min(d[index + 5] + g * v10, 255);
      d[index + 6] = Math.min(d[index + 6] + b * v10, 255);
      d[index + 7] = 255;
    
      index += canvas.width * 4;
    
      d[index + 0] = Math.min(d[index + 0] + r * v01, 255);
      d[index + 1] = Math.min(d[index + 1] + g * v01, 255);
      d[index + 2] = Math.min(d[index + 2] + b * v01, 255);
      d[index + 3] = 255;
    
      d[index + 4] = Math.min(d[index + 4] + r * v11, 255);
      d[index + 5] = Math.min(d[index + 5] + g * v11, 255);
      d[index + 6] = Math.min(d[index + 6] + b * v11, 255);
      d[index + 7] = 255;
    }
    
    function updateCanvas() {
      context.putImageData(canvasData, 0, 0);
    }
    
    function clearCanvas() {
      for (var i=0; i<canvasData.data.length; i+=4){
        canvasData.data[i]=0;
        canvasData.data[i+1]=0;
        canvasData.data[i+2]=0;
        canvasData.data[i+3]=255;
      }
      context.putImageData(canvasData, 0, 0);
    }
    
    </script>
    
    



  • 0
    Увидел от имбриона до саламандра
    • +1

      Во втором слове ошибка, проверочное слово — сало. Надо писать, — саломандр.

      • 0
        А в первом — имбирь?
        • +2

          Эбсэлютли!
          Хотя, есть ещё вариант с имбецилом.

  • 0
    Чтобы устранить из алгоритма человека можно в качестве fitness использовать какую-нибудь картинку. Того же дракона, например. Тогда можно реально картинки превращать во фракталы.
  • 0
    Хорошая работа. Очень круто. Согласен, 3D всегда лучше. Будут еще аналогичные статьи?
  • 0
    Тоже в универе рисовал фракталы, только с использованием систем итерированных функций (СИФ). Написал программу, позволяющую пользователю интерактивно менять параметры СИФ и сразу видеть полученный фрактал
  • +1
    Очень интересная статья!

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.