Все началось из увиденной в плеере AIMP визуализации Phthalo’s Corona. Я долго думал как она работает и наконец кое-что придумал.
Первый прототип был написан на C++.
В итоге у меня получился только прототип движения частиц как в этой визуализации. На этом я и остановился и через пол года снова захотелось поработать со звуком. Через 2 дня была готова вот такая флешка.
ССЫЛКА
Для того, чтобы она корректно работала, нужно открыть какой-нибудь музыкальный плеер(например в ВКонтакте) в браузере, и закрыть все лишние вкладки с аудио данными, потому что при получении спектральных данных, через SoundMixer.computeSpectrum флешка может вылетать из-за, того что другие приложения не позволяют брать у них аудио данные. Если не падает, то можно ничего не закрывать. Ну как-то так 🙂
Теперь перейдем к обзору того, как это все работает.
Физика
В области расположены частицы(в данном случае 650). 600 активных частиц и 50 частиц, которые появляются в случайной точке области и падают в низ, когда такая частица касается пола, она заново респаунится в области. Само собой на все частицы действует гравитация.
По мимо гравитации на частицы можно влиять аттрактором и репульсором. Сейчас я объясню, что это за штуки такие волшебные. На самом деле, они никакие не волшебные, просто названия у них такие же заумные, какими любят выражаться всякие «профессора».
Аттрактор — это такая штука, которая притягивает к себе все, что только можно. В данном случае частицы, причем чем дальше частица от аттрактора, тем слабее она притягивается.
Репульсор — тоже самое, что и аттрактор, только он наоборот отталкивает все от себя.
Так вот, на частицы при определенных условиях(основываясь на звуковом спектре) действует аттрактор и репульсор. Из-за этого они так скачут в области.
Отрисовка
Создается bitmapdata для частиц, одна для всех(это логично :)). Это маленький белый кружок, почему белый объясню ниже.
var radius: Number = PARTICLE_SIZE * 0.5; var particleShape: Shape = new Shape(); particleShape.graphics.beginFill(0xFFFFFF); particleShape.graphics.drawCircle(PARTICLE_SIZE * 0.5, PARTICLE_SIZE * 0.5, radius); m_ParticleBmp = new BitmapData(PARTICLE_SIZE, PARTICLE_SIZE, true, 0x0); m_ParticleBmp.draw(particleShape);
Рисуются частицы так:
var velDir: Number = Math.atan2(part.vel.y, part.vel.x); var speed: Number = Math.max(Math.abs(part.vel.y), Math.abs(part.vel.x)); if (speed > 20) speed = 20; matr.identity(); matr.scale(1.3 + speed / 20.0 * 4.0, 0.5); matr.rotate(velDir); matr.translate(part.pos.x, part.pos.y); m_BackBuffer.draw(m_ParticleBmp, matr);
Матрица трансформирует частицы так, что они растягиваются при движении. Ничего сложного.
Эффекты
Палитра
При запуске визуализации, создаются палитры из которых идет выборка цвета при отрисовке конечного изображения(стандартная фитча демокодеров 🙂
Они рандомно выбираются в процессе работы.
Для работы с палитрой, необходимо заготовить 3 массива размером 256 значений. Это будут массивы для красного, зеленого и синего цветов.
Для того чтобы их задействовать, необходимо воспользоваться функцией paletteMap класса BitmapData. Она получает в качестве параметров как-раз таки эти массивы и исходное изображение.
Работает это так:
- берется цвет пикселя из исходного изображения, integer;
- этот цвет разбивается на rgb(byte) компоненты;
- по этим компонентам, берется значение из соответствующего массива, это и будут новые rgb значения;
- новые rgb значения собираются обратно в integer.
Теперь объясню, почему частицы нужно рисовать белым, а фон черным.
При применении блур фильтра, все пиксели будут размываться и в итоге, если ничего не рисовать, они станут черными. При применении палитры, черный заменяется на любой цвет, таким образом при размытии цвет будет плавно переходить в нужный нам цвет(в буфере, в который рисуются частицы, фон останется черным).
Палитра строится так, что в верхних индексах (255 — белый цвет) rgb массивов, записывается цвет частиц, а в нижних (0 — черный цвет) цвет фона. Во всех остальных индексах [1..254] записываются цвета градиента от цвета фона до цвета частицы. Конечно никто не мешает добавить больше 2 цветов в палитру и сделать градиент между ними.
Distortion map
В оригинальной визуализации, сделано очень интересное завихрение следа частиц. Я долго всматривался в него, чтобы понять как оно работает.
В итоге я пришел к выводу, что это гиперболическая спираль.
Уравнение гиперболической спирали(в полярных координатах):
r * phi = a, где a — это скорость расхождения спирали
В декартовых координатах:
x = a * cos(phi) / phi,
y = a * sin(phi) / phi, где a / phi = r
Встал вопрос как теперь это сделать О_О. Во первых, нужно как-то задать формулу для такого дисторшина, во вторых как это сделать на флеше.
Конечно же сначала я попробовал сделать все по старинке, пройдясь по всем пикселям изображения и сдвинуть пиксели в определенных направлениях, к тому же это позволило бы сразу же туда засунуть и размытие. Но на практике оказалось все намного иначе. Цикл по изображению убивал fps. Пришлось искать другое решение.
В голову пришла идея сделать twirl эффект, сразу нашел код через DisplacementMapFilter. Но это было не то, что было в оригинале.
Пришлось вернутся к обзору гиперболической спирали. Вся трудность заключалась в том, что спираль четко задается через угол и радиус в полярных координатах, а мне нужно было добавить еще один параметр — толщину. В итоге после вывода некоторых формул, я написал алгоритм генерации карты смещений.
Карта смещений строится по такому алгоритму:
Пробегаясь по всем пикселям изображения, каждый пиксель проверяется на принадлежность спирали(учитывая толщину). Если пиксель принадлежит спирали, то в этой точке необходимо вычислить смещение.
Для нахождения направления смещения можно взять касательную в данной точке. Для упрощения расчетов я решил находить касательную к окружности в данной точке. При таком подходе, погрешность расчетов будет заметна только в хвосте спирали. Т.к. при увеличении радиуса(или угла) y координата стремится к a, т.е. к линии.
f'(x,y) = ArcTan(y/x) — Pi / 2
Теперь просто находим вектор по полученому углу и умножаем его на коэффициент смещения. В итоге получилась такая карта смещений(используется зеленый и синий каналы)
Во втором режиме, накладывается другая карта смещений которая генерируется шумом Перлина.
m_NoiseBuffer.perlinNoise(SCREEN_WIDTH, SCREEN_HEIGHT - FLOOR_POS, 2, 1, false, true, BitmapDataChannel.RED | BitmapDataChannel.BLUE, false, [m_OctaveOffset, m_OctaveOffset]);
Вот такое симпатичное изображение получается на выходе:
Можно было бы добавить больше октав при генерации, но шум генерируется каждый апдейт(для этого и используется смещение октав, для эффекта движения).
В итоге шаги отрисовки такие:
- К backBuffer применяется DisplacementMapFilter фильтр со смещениями.
- К backBuffer применяется фильтр размытия(x: 8, y: 8, quality: 1).
- К backBuffer применяется ColorTransform, так мы регулируем скорость затухания изображения.
- Рисуем частицы в backBuffer.
- К screeBuffer применяем paletteMap, в качестве исходного изображение применяем backBuffer.
- Ну и последнее рисуем перевернутый кусочек пола
На этом все, спасибо за внимание!
ссылка на оригинал статьи http://habrahabr.ru/post/155695/
Добавить комментарий