Pull to refresh

Comments 85

Немного не в тему. Но могу добавить восторженных впечатлений от журнала "В мире науке". Фантастически интересный журнал 80-х годов. Когда-то прочитала там про redcode. Настолько зацепило, что уже позже в 90-х написала собственный redcode c интерпретатором и средой выполнения. Дело было ещё на PC XT под DOS на С. Жаль, что с тех пор не осталось ни журналов, ни дискет. Всё как-то и куда-то складывалось, а потом перестало быть в неизвестном направлении.

А я помню статьи про бои в памяти! Целый цикл был, но для меня он оказался сложноват.

UFO just landed and posted this here

Вот тут еще масса идей и картинок

Книга очень интересная, но она немного о другом. Точнее немного о другом типе картинок.

Я еще в универе случайно открыл похожую формулу в виде

      x := Frac(x+sin(y));
      y := Frac(y+cos(x));

и все эти годы не мог найти упоминаний о ней и объяснения получающихся картинок. Спасибо за статью!

Frac - что за функция? Возвращает дробную часть?

да. Ну и координаты соответственно получаются от 0 до 1, для рендера надо умножить на размер окна.

А x и y в каком диапазоне должны быть?

Со второй итерации они попадут в диапазон [0;1]

А есть примеры картинок, полученных именно по Вашей формуле?

У меня так получилось.

Прикольная абстракция! Здесь цвет отображает плотность аттрактора при 100 млн итерациях. Интересно, что от начальной точки итерации изображение не зависит -- оно является инвариантом указанного преобразования.

Непонятно, по какой формуле цвет из плотности рассчитывается? В ч/б варианте (каждая точка просто черным цветом рисуется) у меня совершенно другая картинка получилась (см. ниже).

Можете полный псевдокод процедуры дать?

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

Попробовал сделать несколько картинок по этой формуле. Начальные значения: x = 0.5, y = 0.3. При изменении количества итераций (в тысячах) 50 => 100 => 200 => 500 => 1000 картинка меняется так:

Видно, что поле всё больше заполняется точками, но остаются пустые "дырки". Интересно, это следствие конечной точности вычислений? Я все вычисления делал в double (для получения координат, понятно, после умножения x на ширину и y на высоту приводил значения к int).

У нас разница в том, что в моём случае новые значения x и y вычисляются одновременно, а в вашем — последовательно. Мой алгоритм будет выглядеть так:

z = x
x = frac(z - sin(y))
y = frac(y - cos(z))

При этом старое значение x сохраняется после вычисления строки 2.

А если просто вычислять

x = frac(z - sin(y))
y = frac(y - cos(z))

То после выполнения строки 1 значение x уже изменится и это изменение пойдёт в строку 2

Я уже понял свою ошибку. В случае с формулой Мартина, если не сохранять x в промежуточную переменную, x и y очень быстро расходятся в бесконечность.

На мой взгляд, важное замечание: тому, кто решит реализовать подобный код сам, особенно на других языках программирования, стоит иметь ввиду, что здесь для рисования точек используется не просто setPixel(x, y, color). Я посмотрел Ваш код скрипта, и заметил следующие моменты:

ctx.globalAlpha = 0.1

Я не силен в JS - здесь устанавливается глобальная константа смешивания цветов точек при рисовании?

Ну и сама точка рисуется как прямоугольник:

ctx.fillRect(W/2+x*W/S,W/2+y*W/S,0.5,0.5)

Правильно ли я понимаю, что это прямоугольник с высотой и шириной 0.5 пиксела? Или с такой толщиной линии? Поясните, пожалуйста.

Я это к тому, что если такое делать на C++, как пытаюсь делать я, то нужно предпринимать какие-то ухищрения, чтобы так же рисовать. Например, формировать массив точек в памяти и там же смешивать цвета точек, если они попадают в одни и те же координаты.

Совершенно верно, на C++ надо делать тот или иной вид субпиксельного сглаживания. Именно такое смешивание, как вы описали, я и сделал для картинки выше: подсчитывал число попаданий точек в ячейку массива и отображал таким образом плотность.

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

Смешение цветов предпочтительнее, оно позволяет получить эффект "высокого разрешения", плавных переходов и тонких деталей.

в общем вот на паскале код:

  RandSeed := 1;
  for i := 1 to 100 do
  begin
    x := random;
    y := random;
    color := Random(MaxLongint);
    for j := 1 to 1000 do
    begin
      pr2d_Pixel(x*1000,y*1000,color);
      x := Frac(x+cos(y));
      y := Frac(y+sin(x));
    end;
  end;

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

У вас как и выше в треде x,y последовательно друг друга портят... Надо z=x;x=...,y=...(z);

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

вот наугад добавил коэффициенты -

      x := Frac(x+cos(1.5*y)-0.2);
      y := Frac(y+sin(x));

Интересно, надо будет попробовать.

Попробовал Ваш код, только увеличил число шагов во внешнем цикле до 500, а также цвет беру не совсем случайный, а случайный из фиксированного набора в несколько цветов. Также, при добавлении пиксела в точку смешиваю цвет с тем, что там уже есть, с весом 0.5.

ИМХО, довольно красиво получается:

Попробовал вариант и с дополнительными коэффициентами. В формуле Frac(x+cos(1.5*y)-0.2) вместо 1.5 генерирую случайное число в диапазоне от 0 до 1 и прибавляю 1, вместо 0.2 - еще одно случайное число от 0 до 1. Иногда интересные результаты получаются, например:

Надо еще какие-нибудь функции попробовать вместо sin/cos. В общем, бездна для творчества :-)

из функций я еще пробовал в свое время заменить синус и косинус на первые члены ряда тейлора (sin на x, cos на 1-x*x/2) и на (tan, 1-tan). Ну и да, коэффициенты. Моей мечтой было как-то сделать этот мир интерактивным, чтоб можно было "исследовать" отдельные области (как с фракталами), но каждая картинка на тогдашнем компе рисовалась секунды, так что далеко от этих картинок не ушел.

Из этой статьи я примерно понял хотя бы идею - должен быть поворот на 90 градусов+нелинейное искажение.

Анализируя вашу схему я бы тоже сначала рассмотрел первые члены рядов для синуса и косинуса.

Поворот не обязателен. В вашем случае роль складывания после искажения играет вычисление дробной части, то есть отображение всей плоскости на квадрат со стороной 1. Именно сочетание искажения и складывания обеспечивает перемешивание. А будут при этом неподвижные точки простых типов или странные аттракторы, конечно, зависит от конкретного преобразования.

Что-то мне подсказывает, что здесь периодичность функций ещё имеет значение.

Например, если заменить sin(x) на sin(x*x), картинка сразу в кашу превращается.

Ещё я попробовал заменить sin на функцию Бесселя, т.е. затухающую - то же самое. Явно должны быть периодически повторяющиеся значения, чтобы при отображении на квадрат возникали круги, а не просто каша из точек.

А какие ещё есть периодические функции, отличные от sin/cos?

аргумент в синус приходит от 0 до 1, т.е. периодичность не провляется. И можно вообще заменить sin(x) на x - картинка принципиально не поменяется (станет чуть более скучной).

Хотелось бы понять математические причины, почему все-таки синус и косинус дают такую картинку с фоном и кругами, внутри которых окружности возникают.

Попробую объяснить в двух словах. У отображения двумерного пространства на себя, сохраняющего площади окрестностей любой точки могут быть неподвижные точки, то есть, не изменяющиеся под действием отображения. У гладких отображений неподвижные точки бывают принципиально двух видов: эллиптические и гиперболические (вырожденные случаи тоже могут быть, но они нетипичны).

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

Переход от упорядоченных орбит к хаотическим может быть постепенным (через каскад удвоения периода) либо резким в случае развитого хаоса. Более или менее подробно я описывал это в статьях про прыгающий шарик (https://habr.com/ru/articles/750380/). В вашей схеме некоторые гиперболические точки выдают себя на картинках высокого разрешения крестообразными сгущениями орбит.

Не скажу, что я много что понял :) статью по ссылке почитаю, спасибо. А какая это область математики?

Теория динамических систем и теория хаоса.

А есть какие-то "жизненные" применения этих областей? Моделирование взрывов?

Моей мечтой было как-то сделать этот мир интерактивным

Тут есть ссылки на две реализации отрисовки в браузере на JS - подозреваю, если переписать отрисовку по sin/cos, должно работать и быстро, и интерактивно, как во втором примере с бегунками для параметров.

Как в демке осуществлён переход от двумерной формулы к 3D? Какой-то параметр взят за третью координату?

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

Интересно, как это всё реализовано. Во время "полёта" заранее просчитывает сколько-то картинок? На каждую нужны десятки, а то и сотни тысяч итераций.

Ну мой не то что бы оптимизированный вариант на JS при определенных значениях генерит вполне похожую картинку за 20к итераций что занимает 11мс, при том что для 60фпс надо уложится в 16... Это если по картике на кадр, а там куда реже генерация, так что если еще и в веб поркере делать...

Я так понимаю, алгоритм распараллелить не получится. Зато у современных процессоров есть много ядер. Можно в фоне несколько картинок генерировать в отдельных потоках, а показывать их, когда готовы.

Ну потому и сказал про WebWorker... Плюс даже на максимальной скорости полета там вроде не более 5ки картинок в секунду... Тут даже без многопоточности ресурсов достаточно.

Глядя на то, с какой скоростью работает JS в браузере, сдается мне, что отрисовка использует аппаратные возможности GPU. Программно в памяти смешивание цветов с весом явно медленнее работает. Возможно, у меня руки кривые, но я реально тут вижу случай, когда что-то по-быстрому сделать на JS получается и проще, и быстрее, чем на C++.

Использует, но справедливости ради, хороший программный растиризатор тоже удивительно быстрым может быть.
Тот же хром если отключить апаратку даже 3д не так уж медленно рисует...
Те в плюсах и жсе сравнивать надо не их, а растеризатор в первую очередь.

А еще мне приходит в голову генерить это на шейдере, в том смысле что есть зависимость от сгенеренных, но в то же время сгенерив пару тысяч, мы можем шейдером каждым прогоном удваивать результат. А видеокарты щас быстрые.
На самом деле это будет не полностью идентичный последовательной генерации результат, ибо погрешности попадения в пиксель будут, но близко думаю.
Я проверил, если округлять границу пикселя, то все становится скучно, но если добавлять рандом после округления, то картинки визуально не отличимы от оригинала, не смотря на постоянное округление. (Тоже самое было бы с шейдером, но его я щас писать не готов)

Вообще может быть интересно рисовать на хосте, на который натравлять шейдер с инвертированной формулой. Те мы как бы будем своим рисованиям предоставлять x,y исходные...

Спасибо за отличную статью и воспоминание о прекрасном журнале! В одном номере "В мире науки" я познакомился с петлевой квантовой гравитацией... Шикарный журнал)

Похоже на множество Мандельброта. Там даже соревнования проводятся по рисованию :-)

Кстати, в том же журнале, но в номере 10 за 1985 год, и про множество Мандельброта есть.

Жаль, значения параметров ограничены значением 10. В журнале, о котором идет речь, приведены интересные картинки, для получения некоторых используются значения a = 200, с = 80. А еще там есть пример с отрицательными значениями параметров.

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

В журнале пример a=-1000, b=0,1 и с=-10. Правда, картинка у меня получилась почему-то совсем не такая, как в журнале.

Я думаю, вряд ли можно подобрать какой-то диапазон "на все случаи жизни".

Спорить не буду, но по мне так вообще не интересно, и +- вчера у меня на положительных что-то похожие было... А вот [-50,50,1000], 0.1, -10 выглядит куда интереснее. Но выставил +-2000, и scale от 0.1. Вообще если что на 24 строке исходника сами можете поправить лимиты, там без регистрации можно...

У меня аватарка похожим образом построена.

Hidden text

Код там правда был ужасный. Написано еще в студенчестве на редком диалекте паскаля Алго. А формулу вообще не помню откуда взял.

Красивый пледик, без шуток.

Hidden text

Я тут немного не в тему, вспомнился старый пост <<Логотип за три доллара>>. И там был пёс-арбуз с COM портом вместа рта. Что-то найти не могу.

У меня бабушка очень похожие коврики напольные вязала.

У меня такой вот коврик получился )

Аттрактор "Hopalong"
Аттрактор "Hopalong"

Сигнум, он же знак. Может встретиться в виде sgn(x)

Понял, так и думал, хотел уточнить.

Есть где-нибудь более подробные статьи про данную тему? Попытался загуглить "итерационная схема Барри Мартина", ничего вразумительного не нашел.

Боюсь, что я создал нетленку -- то есть единственный на русском языке материал, посвящённый именно этой схеме. Если поискать в англоязычном сегменте (hopalong attractor, Martin's hopalong), то будет много этюдов по программированию на разных языках, демок или записей в блогах, а также несколько статей (https://jolinton.co.uk/Mathematics/Hopalong_Fractals/Text.pdf), посвящённых генеративному искусству, но не разбору динамики.

Множество интересных схем есть здесь: https://paulbourke.net/fractals/

Всё это очень интересно, спасибо. Помню, в 90-е годы на одном местном телеканале была заставка с подобными анимированными картинками.

При многократном применении (итерациях) 

Сколько итераций обычно требуется на построение одной картинки? Есть ли какой-то вычисляемый критерий, по которому программа сама может понять, что можно остановиться?

Все зависит от параметров, но ИМХО можно привязаться к дистанции от старта. Ибо точки обычно ложатся сериями в рамках старого рисунка.

Я мимо проходил, но кажется это фракталы.
А по фракталам есть вузовские учебники. Как минимум нам откуда-то курс по ним читали

Если я правильно помню, фракталы состоят из элементов, каждая часть которого рекурсивно заменяется на вложенный такой же элемент, и так до бесконечности. Здесь, вроде бы, не совсем такое. Если я не прав, пусть меня поправят.

Фрактальные объекты бывают самоподобны, но могут такими и не быть. Их главная особенность -- дробная размерность Хаусдорфа. Приведённый в статье объект фрактален, но не самоподобен.

Интересно, с какими значениями параметров удается получить картинки с "пледиками", что в начале статьи? Или как на обложке журнала.

Нашел скан журнала в PDF. Кое-какие интересные примеры значений параметров в нем приведены. Например, такие:

Однако, по картинке пришлось изрядно покликать мышкой, чтобы такое получить.

У меня такие круги получились как то, типа дифракционных колец Френеля )

Fresnel diffraction fractal
Fresnel diffraction fractal

Каким алгоритмом такое получили?

procedure Fresnel;
const
window_size = 10.0;
var
x, y: integer;
dx, hw, hh, m, n, f: double;
lens: array of array of double;
begin
hw := Width * 0.5;
hh := Height * 0.5;
dx := window_size / Width;
SetLength(lens, Width, Height);
for x := 0 to Width - 1 do
for y := 0 to Height - 1 do
lens[x, y] := cos(Density * (sqr(dx * (x - hw)) + sqr(dx * (y - hh))));

m := -MaxInt;
n := MaxInt;
for x := 0 to Width - 1 do
for y := 0 to Height - 1 do
begin
m := Max(m, lens[x, y]);
n := Min(n, lens[x, y]);
end;

m := (m - n + 1e-10);
for x := 0 to Width - 1 do
for y := 0 to Height - 1 do
begin
f := (lens[x, y] - n) / m * 100;
Bmp.Pixel[x, y] := ColorMap.IterToColor(f);
end;
end;

sqr(dx * (x - hw)) + sqr(dx * (y - hh))

Когда x превысит половину ширины или y превысит половину высоты, под аргументом квадратного корня окажется отрицательная величина. Нужно взять её абсолютное значение?

Понял, sqr - это квадрат, а не квадратный корень :-)

Какая у Вас таблица цветов используется? Я использовал палитру {'black', 'yellow', 'red', 'orange', 'blue', 'cyan', 'magenta', 'green'}, картинка больше синяя получается, чем зеленая:

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

Еще можно квадраты на кубы заменить:

Имхо, эти кучки чем-то на игру "жизнь" похожи, только не с полным уничтожением :)

Попробовал алгоритм CIRCLE2 из статьи.

Использовал вариант с несколькими фиксированными цветами, а не с двумя. Довольно симпатично, но картинки не столь же интересные, на мой взгляд.

Sign up to leave a comment.

Articles