«Живий» прогноз погоди, або Як використати генеративне мистецтво у вебі

У рубриці DOU Проектор всі охочі можуть презентувати свій продукт (як стартап, так і ламповий pet-проект). Якщо вам є про що розповісти — запрошуємо взяти участь. Якщо ні — можливо, серія надихне на створення власного made in Ukraine продукту. Питання і заявки на участь надсилайте на [email protected] .

Мене звуть Мар'яна, я випускниця програми 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 . Дані опрацьовуються, а саме переводяться в зрозумілу для системи шкалу.

Трішки грі з кольорами для різних пір року, застосування реальної постривай — і рік крізь це «живе вікно» на відео матиме такий вигляд:

Проект, звісно, може бути покращено багатьма способами, де першочерговим стані оптимізація рендерингу. Наразі рендеринг сторінки залишається доволі трудомістким та займає велику частину оперційної пам'яті. Так стається через те, що після обчислення позицій елементів картини з новим показником вітру цілий канвас промальовується та зберігається для того, аби бути використаним ще раз у майбутньому при такому ж вітрі. Так зменшується кількість калькуляцій в секунду, проте зростання потреба в оперційній пам'яті.

Розширювати проект можна безмежно!!! довго, додаючи можливість бачіті погоду в інших містах, з різним кліматом, а через це й різним ландшафтом і деревами. А також додати зміну дня і ночі та інші природні явища: вітер, ураган, блискавку, торнадо тощо. З таким спектром можливостей застосунок міг би використовуватися на сайтах прогнозів погоди.

Дякую за увагу!

Опубліковано: 05/11/19 @ 11:00
Розділ Різне

Рекомендуємо:

Рейтинг ІТ-роботодавців 2019: опитування
BA дайджест #5: архітектура підприємства, формальні методи валідації UI
Як в DB Best створили BI-проект статистики ігор в покер
5 книг, які навчать мислити системно, від Кирила Білобородько, Software Engineering Manager в EPAM
OS Daemonology: види, переваги, підводні камені