Стендап Сьогодні
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
Пости з тегом #ПостПроПохід
09.04.2023
Почав робити фотозвіт про походи
В рамках фреймворку “хочу — не хочу” взявся, нарешті, за звіти про подорожі. Річ у тім, що в мене лежать сотні красивих та цікавих світлин, з яких давно мрію збирати звіти, але, як це буває, ніяк не знайду часу.
Одним з аспектів прокрастинації є те, що хочеться відразу дуже технічно крутий підхід, на кшталт статей з New York Times з анімаціями, картами, крутою типографікою і так далі. Звісно, все це я не вмію робити. Тому, щоб зрушити справу, хочу почати з чогось простого На жаль, я не ідеальний, тому найпростіше рішення — пост зі світлинами — мене не задовольняє. Як не задовольняє і галерея без тексту.
Зі зробленого сьогодні: обрав похід (а це теж не так просто; треба було щось ізольоване, але цікаве); відфільтрував світлини від дублікатів та сміття; написав текст. Тепер залишилось зібрати все до купи та, напевно, видалити ще багато фотоматеріалу.
Для перегляду світлин обрав поки lightGallery. За задумом галерея має бути не одна, а по одній на кожний параграф, та ще й з цікавим макетом. Тож наступна технічна задача — організувати структуру документа з тексту та галерей. Та, в ідеалі, щоб все залишалось в Markdown.
Знайшов отакі рекомендації по розміру картинок від Shopify; виходить 2240x1260 пікселів як максимум. До речі, щоб підігнати картинки під розмір однією командою — беру ImageMagick - командою mogrify -resize 2240x1260 *.jpg
. А для остаточної оптимізації розміру є утиліта ImageOptim, якою я теж постійно користуюсь.
Ще є корисний додаток PeakFinder, який підписує гори; причому він вміє це робити як по камері, так і по старій світлині. Та, він навіть працює на macOS, хоча і в версії для iPad.
(Якщо є готові, відкриті інструменти для створення матеріалів “як у Нью-Йорк Таймз”, буду дуже рад дізнатись.)
22.04.2023
Прогрес по фотозвіту про походи
Продовжую роботу над своїм “журнальним форматом” звітів про подорожі на основі Hugo. Прогрес величезний, але показувати поки рано. Залишилось, головним чином, адаптація під маленькі екрани, та редакторська робота над самим постом.
Щоб додати новий різновид постів, в Hugo є типи контенту. Для даної задачі головне, що в кожного типу — свої шаблони. Подорожі будуть представлені в широкоформатному вигляді, без сайдбару, який є у звичайних постів — тож без нового шаблону не обійтися.
Далі, ідея в тому, що зміст поста, з усіма світлинами, відповідає домовленостям Hugo. Тобто пост пишеться в Markdown. Як до Markdown додати “журнальний формат”? Власними шорткодами. Шорткоди можуть додавати до тексту будь-яку розмітку. Вони можуть як вставляти її (наприклад, шорткод світлини), так і загортати текст (так можна створити виноски тощо.)
Моє бачення задачі таке: зміст поста розбивається на презентаційні блоки, впорядковані вертикально. Кожний блок матиме різний формат — це може бути текст, світлина, галерея тощо. Конфігурація блоку задається в метаданих поста; якщо потрібний текст, то він пишеться прямо всередині. Точніше навпаки: шорткоди, що створюють блоки, пишуться всередині тексту; так його легше редагувати, ніж коли текст порізаний на YAML або інший конфігураційний формат. Власне, пост пишеться як звичайно, тільки доповнюється шорткодами — і це дуже важливо, бо реалізація робиться не на один пост, а на майбутнє.
На останок додам, що сучасний CSS - це приємно. Я людина травмована попередньою епохою верстки на float
ах. Флоати були складною та неприродною поробкою. Плюс до того браузери працювали по-різному. Тому CSS був езотеричною технологією, де без глибокого досвіду було важко зробити щось красиве, зате легко — поламати.
Зараз все набагато краще. Нюанси поведінки браузерів практично зникли. Верстка на flexbox
підтримується у 99% клієнтів. Модель flexbox
прозора, її легко опанувати, та вона робить те, що від неї очікуєш. Багато всього, що раніше робилось за допомогою JavaScript, тепер вирішується засобами CSS. От сьогодні дізнався про scroll-snap; чудово!
Тож раджу спробувати, може й тебе CSS приємно здивує.
29.04.2023
Роблю глобус на three.js
Для постів про подорожі намагаюся зробити красиву тривимірну карту. Причому через мої дикі вимоги “просто” карта на кшталт Google Maps або Leaflet не подобається — роблю свою. Ідея в тому, що замість повноцінної карти це буде обмежена та зрежисована анімація.
Вирішив робити на three.js - бібліотеці тривимірної графіки на JavaScript. На сучасних пристроях вона повністю прискорена технологією WebGL та графічним процесором, тому працює все дуже добре і можна робити речі набагато складніше, ніж глобус.
Перший крок — власне, намалювати глобус. Для цього знайшов класну статтю; код з неї запрацював майже без корекції на сучасну версію three.js. Потім достатньо легко зʼясував, як перекладати географічні координати на “графічні” (для цього дуже корисним є клас Spherical) та орієнтувати глобус на той бік, що мені потрібно. Навіть анімувати переліт з однієї точки в іншу.
Далі задача була така, щоб камера наближувалась до об’єкта, а карта збільшувала масштаб в міру наближення. Як всі нормальні карти роблять — але в мене ідея завантажувати потрібні мені шари карти заздалегідь, щоб це було гладенько. Власне, зображення плиток можна взяти багато де, наприклад, на MapTiler. Залишається накласти їх на глобус. І тут все пішло не так…
Якщо дуже коротко, то стандартна проєкція онлайн-карт — Меркатор — не сходиться зі стандартним способом накладання текстури на сферу. Меркаторівська проєкція витягується біля полюсів. Тому просто накласти текстуру на глобус — як це зробив автор статті-прикладу — можна тільки якщо карта підготована “в форматі текстури”. Щоб відображати карти, треба обчислювати текстурні координати для всіх точок на сфері. Тут вже починається серйозна тригонометрія, на якій я поки застряг — може тому, що вже друга година ночі.
А ще, сфера Three.js хороша, як сфера, але не як глобус. ЇЇ розподіл на полігони більш нагадує волейбольний мʼяч, ніж глобус. Тому я й генерацію глобуса переписав. Алгоритм досить простий — біжиш по широті та довготі, генеруєш трикутники парами. А ось тригонометрія складна. На ній поки й залишаю.
30.04.2023
Тривимірна карта нарешті працює
Вийшло зробити ті ілюстрації, які хотів — наближення з глобуса до локальної карти, та демонстрація треку на тривимірній карті. Звісно, ще є над чим працювати, але база є.
Щодо глобуса: розібрався з формулою для меркаторівських координат і все запрацювало. Допоміг аркуш паперу та ця сторінка з вікі OSM. Наступний етап — плавне наближення до точки. Окрім інтерполяції камери, що досить просто, треба підміняти текстуру на більш деталізовану. Для цього треба розвʼязати, які текстурні координати будуть у деталізованої текстури — над цим теж довелося помізкувати. (Я заздалегідь завантажив кожний рівень деталізації для даної точки на карті. Для цього є ще скрипт на Ruby, що вміє звантажити плитки карти та зшити їх разом за допомогою ImageMagick.)
Щодо локальної тривимірної карти: для цього починаю з локальної карти (такої самої, як останній рівень деталізації глобуса), та додаю до неї Terrain RGB. З даних висоти легко побудувати квадратну поверхню; наклеїти на неї локальну текстуру карти тривіально, бо вона збігається один в один.
Далі, трек; він в мене вже був у форматі масиву координат (які я власноруч записав в поході). Але при переході до локальної карти географічні координати втрачають дійсність, та заміняються системою координат плитки локальної карти, яку ми взяли за основу. Тому тепер довелося розписати формулу, по якій від меркаторівських координат плитки можна перейти назад до граничних значень широти та довготи для неї. А коли є граничні значення, то привʼязати до них трек — задача простого перенесення та масштабування. Надзвичайно приємно, коли трек та карта, взяті з абсолютно різних джерел, сходяться в єдине зображення.
На наступний раз — збагачення карти маркерами, покращення стилю треку, та обʼєднання глобуса та тривимірної карти в єдину сцену.
05.05.2023
Роблю перехід з глобуса на локальну карту
Продовжую змагатись з 3D-графікою, а саме, намагаюсь зробити перехід від глобуса до локальної карти. На перший погляд, задача не тільки проста, але й незрозуміло, що то взагалі за перехід, якщо фактично карта залишається тією самою.
Ось тільки рідко так буває, що 3D “сцена” відтворює реальну структуру фізичних обʼєктів, які вона відображає. Так, хоч локальна карта і є, теоретично, частиною глобуса, але для відображення її в 3D використовувати ту саму модель недоцільно. Глобус складається з тисячі трикутників, тож сторона кожного з них десь близько 500 км. А локальна карта має сторону у 25 разів менше. Тому навіть думати про глобус та карту в одній системі координат незручно.
Отже, після повного наближення “з космосу” до місця треба зробити фокус та підмінити глобус на карту. Цим трюком я й займаюсь. Потрібно зорієнтувати карту в таке саме місце та в такому самому масштабі, та задача зроблена.
От тільки виникла проблема. Який саме масштаб я хочу показати? В попередньому демо локальною картою є просто одна плитка текстури. Але ж для кінцевого варіанту треба вирізати з неї шматок, що відповідає заданому треку. Тут довелося знову пірнати в математику. Координати, що обмежують трек, відомі. По координатах можна вийти на розмір в метрах. Далі в метрах можна обвести трек квадратом, а цей квадрат знову перевести в координати. (Ще цікава річ — координати не відповідають кілометрам однорідно, бо довжина градуса довготи залежить від широти, на якій його вимірювати.)
Потім, маючи координати границь видимої області, можна знайти їх в межах координат плитки (які можна обчислити оберненою формулою від меркаторівської проєкції), та, нарешті, побудувати відповідні координати текстури та висоти нашої локальної карти. Фух! І це ще без анімації.
Чому навчився: налагодити всю цю складну математику легше, коли текстури відображають щось наочне; в мене був прямо прорив, коли я просто намалював на карті коло на місці треку та стрілочку на південь — якийсь компас. Але взагалі існує цілий клас допоміжних текстур в кліточку, по якім можна помітити недоліки текстурних координат.
06.05.2023
Скомбінував глобус та карту
Скомбінував глобус та карту в єдину сцену. Вийшло саме так, як я собі уявляв, хоча анімація поки базова та не вистачає відміток.
Найскладніший аспект всієї цієї задачі — це орієнтація в системах координат. Наприклад, в програмуванні всі звикли, що перша координата (i
) йде вниз, а друга (j
) - ліворуч. Але в графіці перша координата то x
, а y
йде знизу вгору. Коли доводиться генерувати площини та відкладати текстурні координати, то мозок просто ламається. Потім по багато разів передивляєшся код, щоб ніде не було помилок.
Ще — виявилося складним зробити, щоб карта на глобусі відповідала локальній. Якщо в глобусі мало трикутників (наприклад, 2000), то проєкція викривляє текстуру, чого не помітиш, поки не буде потреби “влучити” в точку з точністю до кілометрів. Це вирішується збільшенням кількості трикутників; але в ідеалі, щоб спростити сцену, в міру наближення до точки від глобуса має залишатися дедалі менший шматочок з дедалі більшою деталізацією.
Для того, щоб нормально стилізувати стежку, є клас Line2 - бо стандартна лінія може бути тільки шириною в один піксель. У Three.js багато таких “прикладів”, що часто містять бібліотеку, яку можна використати в власних проєктах.
Нарешті, за допомогою бібліотеки suncalc я визначаю розташування сонця в день походу та емулюю його джерелом світла. Дрібниця, але мені приємно. Та й робиться просто — оскільки карта вже зорієнтована так, що вісь Y вказує на північ, то сонячні азимут та висота збігаються зі стандартними сферичними координатами 𝜑 та 𝜃.
13.05.2023
Анімація за прокруткою
Черговий прогрес по тревел-постам — реалізував анімацію карти за прокруткою. Подивився оцю нещодавню статтю з NYTimes та зрозумів, що саме цього не вистачало для режисованого, але все ж контрольованого користувачем відображення. (Анімація, яку я маю на увазі, починається після перших параграфів статті, до неї треба ще докрутити.)
Як я це робив. Щоб реєструвати прокрутку, роблю елемент необхідної висоти — саме його фізичний розмір буде визначати тривалість анімації. Всередину розташовую власне полотно. Полотно за розміром збігається з вікном. Його позиціювання залежить від положення прокрутки — це або абсолютне позиціювання нагорі чи внизу контейнера, або — повноекранне fixed
, при якому й відбувається анімація.
Щоб стежити за прокруткою, слухаю звичайну подію scroll
. Щоб заощадити ресурси, це робиться тільки тоді, коли елемент з картою видимий; для цього існує цікавий клас IntersectionObserver.
Далі позиція прокрутки перекладається на відсоткову, і починається цікава частина. До цього часу моя анімація була повністю заскриптована, та просувалась за допомогою requestAnimationFrame. Натомість для анімації за прокруткою необхідно задати функцію стану сцени від часу. Я майже все так і робив, от тільки з обертанням карти вийшли деякі складнощі. Окремо треба описати переходи від сцени до сцени, як в прямому, так і у зворотному напрямку — додавання та видалення обʼєктів.
14.05.2023
Бібліотека анімації Greensock
Відкриття дня - бібліотека Greensock. Вона надає зручну структуру для анімації. Я вже мало не написав свою, але випадково побачив в прикладах Greensock та, на щастя, це саме те, що мені було потрібно.
В чому моя задача? Анімація з декількох етапів потребує відстеження як поточного етапу, так і положення в ньому. Greensock дозволяє задавати ці етапи декларативно, та сама бібліотека піклується про їх впорядкування. До речі, вона підтримує й анімацію прокруткою, якою я займався вчора. І помʼякшення анімації вбудоване.
Як воно працює з Three.JS? Взагалі вся анімація являє собою інтерполяцію значень. В базових прикладах по Greensock значення містяться в СSS, але насправді вона вміє інтерполювати будь-які числові атрибути довільного обʼєкту. В тому числі, й векторів Three.JS - або просто звичайних обʼєктів, якщо це зручно. Коли над значеннями, що інтерполюються, потрібно проводити додаткові дії, то їх можна описати в функції onUpdate
.
Що мені не дуже подобається: інтерполювати можна тільки по одному обʼєкту, а з Three.JS зазвичай треба комбінувати декілька (або навіть просто position
та rotation
). В такому випадку створюю окремий обʼєкт-ціль інтерполяції, а з нього вже в onUpdate
розкидую значення куди треба.
Ще довго шукав, як же ж робити переходи від сцени до сцени, тобто наприклад з глобуса на карту. Це ускладнюється ще й тім, що з анімацією за прокруткою цей перехід може трапитись в обидва боки. А ще, як виявилось, функція onUpdate
може викликатись не тільки для поточного етапу, тому там не можна робити логіку визначення сцени. Потім знайшов систему міток та функцію currentLabel, яка саме відстежує поточний етап анімації. Тут ідея така: під час render
перевіряю поточну мітку, та готую сцену для неї, якщо мітка помінялась.
3D-графіка - приємна розвага після системного програмування, поки не примушує згадувати про всякі векторні добутки та кватерніони, про які я вже років 15 не чув.
15.05.2023
Враження від Greensock
…Вчорашнє розуміння про Greensock доповнилось сьогодні тим, що в onUpdate
краще зовсім нічого не перераховувати — саме тому, що ця функція може бути викликана в неочікуваний час та в невизначеному порядку. Як тільки переніс всі обчислення з onUpdate
в render
, відразу зникли баги. А то було таке, що в прямому порядку анімація проходить нормально, а у зворотному — бардак.
Тож тепер підхід такий: Greensock собі анімує все, що потрібно, а потім я в функції render
оновлюю сцену відповідно до поточної мітки, переношу значення в потрібні місця, та, власне, потім роблю малювання. Тепер все передбачувано.
Після цього відчувається користь від бібліотеки: рівень абстракції підвищився. Більше не потрібно думати про стикування частин та про перенос глобальної позиції в локальну до етапу. Можна легко додавати до анімації нові етапи та експериментувати (краще наближати камеру та повертати одночасно? або послідовно? )
Наступним кроком, знайшов ресурс Natural Earth, на якому містяться, окрім іншого, бази даних по різних географічних обʼєктах. Мені цікаві кордони держав та міста. Все це можна завантажити в невідомому мені досі форматі shapefile, до якого, на щастя, є парсери як для JavaScript, та і для Go.
PS: а знаєте, що офісне крісло може вимикати монітор? В цьому винний електростатичний розряд, який створює механізм підйому. Звучить нереально, але в мене таке дійсно відбувається час від часу — встаю з крісла та монітор вибиває. Якби не натрапив випадково на пост, то це б залишалось таємницею. А так, спробую замінити кабель до монітора.
20.05.2023
Приховування обʼєктів в Three.js за допомогою Raycaster
Продовжую роботу над своєю картою - з MVP майже закінчив.
Додав до треку мітки зі світлинами. Як я писав раніше, самі мітки позиціюються завдяки CSS2DRenderer
та працює це чудово Але, окрім того, треба приховувати мітки, закриті іншими предметами, інакше втрачається просторове уявлення.
Щоб визначити, чи є мітка видимою, чи її перекриває якась гора, використовується клас Raycaster. Він дуже просто працює: спочатку треба задати розташування камери та напрямок на мітку, після чого Raycaster вміє знайти точки перетину з обʼєктами, які йому передаєш. В моєму випадку такий обʼєкт тільки один — сама карта. Якщо точка перетину з картою ближче, ніж мітка — мітку не видно. От і все.
Перевірка робиться на render
. До речі, ще я прибрав render
з requestAnimationFrame
та викликаю його в scene.onUpdate
. Так суттєво знизилося завантаження процесора — а перемальовувати без оновлення сцени мені все одно не потрібно.
Все, що робить перевірка — це вішає на мітку CSS відповідний клас. Далі — робота звичайної CSS анімації. Єдиний нюанс, що той елемент, яким керує СSS2DRenderer
, краще розглядати суто як контейнер та всі стилі додавати вже всередині — інакше можна натрапити на несподівані конфлікти.
21.05.2023
Пост про похід
🥳 Нарешті пост про похід закінчений та його можна подивитись: Похід на Клаферкесель. Ви перші глядачі, тож якщо щось поламане, прошу поділитися.
Пару зауважень: треба почекати, поки завантажиться — бо світлин близько 26 Мб. На десктопі чи планшеті має кращий вигляд, ніж на телефоні. Бо я хотів красивий великоекранний досвід, а про телефон думав в другу чергу. В майбутньому, сподіваюсь, придумаю щось гарне для телефону, бо оригінальний задум на маленький екран не перекладається.
Ще, анімацію карти треба прокруткою вниз докрутити до кінця; після цього відімкнеться прокрутка нижче. Я вже бачу, що треба це або пояснювати, або зробити інакше (може, автоскрол.)
Карту довелося обмежити квадратом, бо з прямокутним вікном текстури або виходять надто розтягнуті, або їх просто не вистачає. Над цим ще подумаю, бо в ідеалі карта має бути на весь екран.
А все, що нижче, зроблено на шорткодах Hugo та чистому CSS. Сподіваюся, колись стане опен-сорсом. Майже все легко адаптується під інші розповіді — бо я хочу робити більше постів. А також тому, що для інженера рушій то найцікавіша частина. :)