«Живий» прогноз погоди, або Як використати генеративне мистецтво у вебі
У рубриці DOU Проектор всі охочі можуть презентувати свій продукт (як стартап, так і ламповий pet-проект). Якщо вам є про що розповісти — запрошуємо взяти участь. Якщо ні — можливо, серія надихне на створення власного made in Ukraine продукту. Питання і заявки на участь надсилайте на Данный адрес e-mail защищен от спам-ботов, Вам необходимо включить Javascript для его просмотра. .
Мене звуть Мар’яна, я випускниця програми Computer Science в УКУ. У цій статті я хотіла б розповісти про свій дипломний проект. Його суть у тому, щоб зробити веб-застосунок, який зображатиме реальні погодні умови на прикладі природного пейзажу, створеного за допомогою генеративного мистецтва. Ідея полягає в тому, щоб створити новий підхід до зображення погодних умов, що має спростити сприйняття інформації користувачем.
Що таке генеративне мистецтво
Генеративне мистецтво створюється за допомогою автономної системи, яка сама ухвалює сет рішень, обмежуючись правилами. Автор диктує правила, а система генерує контент. Такий собі дует людини та машини. За допомогою генеративного підходу можна отримати надзвичайно красиві й незвичні картини. Також такий підхід застосовується в гейм-девелопменті. Після лекції на тему генеративного мистецтва від фронтент-розробника Юрія Артюха в мене виникло бажання спробувати зробити щось цікаве й корисне, використовуючи такий підхід.
Усі добре знайомі з наявними прогнозами погоди, у яких зазвичай використовують іконки для передачі інформації. Це лаконічно й просто, однак іконка не може передати всі характеристики погоди, особливо динамічні (такі як вітер). Саме тому мені захотілося зробити своєрідне «живе вікно», яке показувало б погоду в русі.
Особливість створеного пейзажу також у тому, що він завжди унікальний, хоча водночас зберігає глобальне розміщення компонентів. Це зроблено завдяки генеративному підходу до створення елементів і, як на мене, додає елементам природнішого вигляду. Для реалізації проекту використовується JavaScript, а всі елементи намальовані в Canvas. Загалом робота над проектом тривала протягом весни — близько трьох місяців, де значну частину часу забирали експерименти з виглядом компонентів.
Пейзаж складається умовно з чотирьох компонентів: дерево, земля, небо, опади. З такими компонентами зручно зображати пору року й такі погодні умови: дощ/сніг, вітер, хмарність.
Дерево
Дерево — центральний компонент, адже за допомогою нього дуже зручно зображати силу вітру та пору року. Структура дерева за природою схожа на фрактальну, тому дерево генеруватиметься рекурсивним чином, де з кожної гілочки на кінці виходитиме ще дві, аж поки глибина дерева не досягне максимуму. Кожна остання гілочка матиме по листочку на кінці.
Для генерації дерева потрібно заповнити масив гілочками, а саме їхніми параметрами. Кожна гілочка повинна мати свій кут нахилу відносно батьківської гілочки, довжину й колір, який потрібен для листочків. Довжина гілочок обирається випадково між двома сталими числами. Так само визначається й кут нахилу, проте до нього додається чи віднімається кут батьківської гілки. Таким чином, з гілки виростатимуть дві дочірні гілочки, направлені в різні боки.
Крона дерева неоднорідна, як куля, а складається з груп, наче кілька кульок. Це впливає на колір листочків, де одні більш затінені, а другі світліші. Для визначення цього кольору крона дерева ділиться на групи, де кожна група замальовується зліва направо в палітрі від найсвітліших до найтемніших кольорів і назад. Функція divide(start, finish, intervalsAmount, n)
розділяє числовий проміжок між start
та finish
на задану кількість інтервалів intervalsAmount
і визначає, в якому проміжку перебуває n
.
const branchGroupDepth = 10; const leavesGroupSize = 2 ** (branchGroupDepth-1); let groupCounter = 0; function generate(angle, depth, arr) { let leafColor = colors[divide(0, leavesGroupSize, colors.length, groupCounter)]; arr.push({ angle, branchArmLength: random(minBranchLenght, maxBranchLenght), color: leafColor }); if (depth === branchGroupDepth) { groupCounter = 0; } if (depth === 0) { groupCounter++; } if (depth != 0) { if (depth > 1) { generate(angle - random(minAngle, maxAngle), depth - 1, arr); generate(angle + random(minAngle, maxAngle), depth - 1, arr); } else { generate(angle, depth - 1, arr); } } }
Щоб оживити дерево і змусити його рухатися від вітру, функція branch()
постійно перераховує значення координат гілок. Під час вітру кожна гілочка дерева переміщується коловими рухами, де початок гілки — центр кола, а довжина гілки — радіус. Залишається лише знайти координати точки закінчення гілки (ця точка й буде початком наступної гілки).
windSideWayForce
— прорахування напрямку гілки залежно від напрямку вітру.
bendabiityOfCurrentBranch
— прорахування коефіцієнта сили нахилу гілки від вітру залежно від товщини гілки.
calcX(angle, r)/calcY(angle, r)
— функції, що виконують r * cos(angle)/r * sin(angle)
для того, щоб знайти координати точки закінчення гілки.
let branchCounter = 0; const bendability = 2; const leafBendability = 17; function branch(x1, y1, arr, depth, windConfig) { if (depth != 0) { const xx = calcX(dir, depth * branchArmLength); const yy = calcY(dir, depth * branchArmLength); const windSideWayForce = windX * yy - windY * xx; const bendabiityOfCurrentBranch = (1 - (depth * 0.7) / (maxDepth * 0.7)) ** bendability; dir = angle + wind * bendabiityOfCurrentBranch * windSideWayForce; let x2 = x1 + calcX(dir, depth * branchArmLength); let y2 = y1 + calcY(dir, depth * branchArmLength); lines[depth].push([x1, y1, x2, y2]); if (depth > 1) { branch(x2, y2, arr, depth - 1, windConfig); branch(x2, y2, arr, depth - 1, windConfig); } else { branch(x2, y2, arr, depth - 1, windConfig); } } else { const leafAngle = angle + wind * windSideWayForce * leafBendability; leaves[color].push([x1, y1, leafAngle]); } }
Малювання гілок відбувається простою lineTo()
функцією в канвасі. Кожна гілочка — це проста лінія.
Кожен листочок намальований за допомогою двох кривих Безьє, а саме функцією bezierCurveTo()
в канвасі. Кожна крива має три точки: початок (блакитна точка), кінець (блакитна точка) і контрольна точка (жовта). Саме контрольна точка формує вигин кривої.
У канваса є одна особливість: у межах одного path
можна малювати багато окремих фігур, але вони повинні мати однаковий колір тла, а також колір і ширину контуру. Щоб пришвидшити процес рендерингу фігур, усі гілки згруповано за товщиною, а листки — за кольорами, і малюють їх групами. Щоб процес рендерингу став ще швидшим, канвас дерева щоразу зберігається в мапі під ключем — значенням вітру. Завдяки цьому дерево потрібно промальовувати наново, лише якщо такого значення вітру ще не було.
Земля
Земля, а саме трава, — не менш важлива за дерево, адже вона точно так само може зображати вітер та пору року. Щоб створити траву, потрібно згенерувати масив травинок, а точніше їхніх властивостей. Кожна травинка повинна мати свої координати розміщення на екрані, а також свою швидкість реакції на вітер і початковий кут нахилу відносно землі.
Щоб створити враження цілого поля з травою, ближчі травинки повинні мати насиченіші й контрастніші кольори, а найвіддаленіші травинки — згладженіші відтінки. Для цього поле ділиться на горизонтальні сектори, де в кожного є свій сет кольорів для травинок. Щоб під час малювання травинки правильно накладались одна на одну, їх потрібно посортувати за координатою y, щоб спершу малювати дальші травинки, а потім ближчі.
function generate(number) { for (var i = 0; i < number; i++) { var y = random(fieldTopStart, h + fieldBottomDeviation); var x = random(0, w); var colorGroup = divide(fieldTopStart, h + fieldBottomDeviation + 1, fieldAreas, y); var color = colors[colorGroup][random(0, colors[colorGroup].length)]; var angle = random(-maxAngleDeviation, maxAngleDeviation); var speed = random(minSpeed, maxSpeed); dots.push([x, y, color, angle, speed]); } dots.sort(); }
Кожна травинка малюється за допомогою двох кривих Безьє, як це було у випадку з листочками на дереві. Щоб змусити травинку реагувати на вітер, координати контрольних (жовтих) точок і координата кінчика травинки залежать від значення вітру.
Небо й опади
Найважливіші елементи неба — хмаринки та їхня кількість. Загалом хмаринки міняють свій розмір залежно від свого розміщення на небі. Саме небо поділене на умовні горизонтальні сектори, і що вище сектор, то більшого розміру в ньому хмаринки і то швидше вони рухаються вздовж неба.
Кожна хмаринка складається з набору кружечків двох типів. Перший тип — це градієнтний білий кружечок з центром посередині. Такі кружечки розподіляються рівномірно за всією площиною хмаринки. Другий тип — це градієнтний сірий кружечок зі зміщеним донизу центром. Ці кружечки накладаються на нижню частину площини хмаринки і в такий спосіб роблять її об’ємною.
Проте хмаринки не лише рухаються вздовж неба, а й мають властивість рухатися всередині самих себе, змінюючи форму. Тому кожен кружечок нестатичний, а рухається по колу, центр якого для кожного кружечка індивідуальний.
Інші важливі елементи неба — опади, а саме дощ і сніг. Самі краплинки малюються доволі просто — коротенькою вертикальною лінією, товщина якої залежить від рясності дощу. Сніжинка ж малюється за допомогою градієнтного напівпрозорого кола. Особливістю руху сніжинок є те, що вони можуть суттєво відрізнятись за розміром, і більші мають більшу швидкість падіння. Завдяки цьому з’являється відчуття, що більші сніжинки розміщено ближче до користувача.
Результат
Для того, щоб оживити застосунок, у ньому використовуються справжні дані про погоду у Львові на цю мить з ресурсу OpenWeather. Дані опрацьовуються, а саме переводяться в зрозумілу для системи шкалу.
Трішки гри з кольорами для різних пір року, застосування реальної погоди — і рік крізь це «живе вікно» на відео матиме такий вигляд:
Проект, звісно, може бути покращено багатьма способами, де першочерговим стане оптимізація рендерингу. Наразі рендеринг сторінки залишається доволі трудомістким та займає велику частину оперційної пам‘яті. Так стається через те, що після обчислення позицій елементів картини з новим показником вітру цілий канвас промальовується та зберігається для того, аби бути використаним ще раз в майбутньому при такому ж вітрі. Так зменшується кількість калькуляцій в секунду, проте росте потреба в оперційній пам‘яті.
Розширювати проект можна безмежно довго, додаючи можливість бачити погоду в інших містах, з різним кліматом, а через це й різним ландшафтом і деревами. А також додати зміну дня і ночі та інші природні явища: туман, ураган, блискавку, торнадо тощо. З таким спектром можливостей застосунок міг би використовуватися на сайтах прогнозів погоди.
Дякую за увагу!