Создание и визуализация волейбольных схем | 2026-01-01T21:37:01

Вот кстати быстрая запись с экрана, как работает мое приложение по созданию и визуализации волейбольных схем.

Детали реализации тут:

Разработка 3D-редактора волейбольных стратегий в полете | 2026-01-01T21:21:21

Чем я занимался в самолете в/из отпуска и иногда между и после: 3D-визуализация и редактор волейбольных схем для Нади (она — тренер). Этот корт на приложенном изображении свободно вращается, на нем могут быть поставлены игроки, и указан путь мяча и игрока — все в 3D.

Траектория мяча рассчитывается так, чтобы мяч не пересекал сетку при движении из A в B (формула Безье). Игроки могут принимать несколько поз — прямо сейчас есть наспех сделанные позы serve, attack, block, pass/receive. Кстати, из интересного в коде: пришлось прописать немного «волейбольных мозгов». Система сама считает траекторию мяча через кривые Безье так, чтобы он всегда проходил над сеткой. Причем высота вылета зависит от типа действия: для атаки мяч «вылетает» с более высокой точки, чем а для паса. Еще добавил авто-разворот: 3D-моделька сама поворачивается лицом туда, куда она по схеме должна пасовать или бежать.

Дольше и сложнее всего было сделать 3D-модель волейболистки. Для генерации реалистичной волейболистки я использовал сервис tripo3D. Он мне выдал модель в нейтральной позе (бесплатно выдал). Теоретически дальше с помощью Blender и плагина Rigify можно прицепить к ней armature и двигать руки-ноги, за которыми будет пересчитываться модель.

Однако в реальности такой подход не срабатывает: сгенерированная ИИ модель содержит большое количество геометрических ошибок, которые прощает рендер, но не прощает Rigify. Их можно условно разделить на два вида — неверные нормали полигонов и проблемы с немногообразной (non-manifold) геометрией, которые исправлять значительно сложнее. Внутри корпуса могут «плавать» невидимые кластеры полигонов или пересекающиеся поверхности. Когда Rigify пытается рассчитать веса (какая кость на какую часть кожи влияет), этот внутренний шум сбивает алгоритм с толку, и в итоге веса распределяются хаотично (например, движение руки может начать тянуть за собой сетку на животе). Плюс модель немного не симметрична.

Non-manifold — это ошибка геометрии, при которой топология объекта перестаёт быть корректной с точки зрения трёхмерного тела: рёбра могут принадлежать более чем двум полигонам, полигоны могут соприкасаться только вершинами или рёбрами без общего объёма, внутри модели появляются «висящие» поверхности или нулевая толщина. Такая геометрия формально не описывает замкнутый объём, из-за чего возникают проблемы с риггингом и деформациями. Кроме этого, нужно упростить модель, потому что для рендера в реальном времени в браузере миллионы полигонов не нужны.

Я исправлял это с помощью MashLab, попутно дорабатывая «напильником» (руками). В итоге получается модель, чуть-чуть отличающающаяся от исходной почти везде. На исходной же модели нацеплена «кожа» в виде текстуры — лицо, майка, шорты должны быть раскрашены. Как все это перенести на упрощенную модель? Для этого есть специальная операция в Blender, называется Baking. Там тоже шаманство. В итоге неидеально перенеслось, но идеально пока и не нужно.

Дальше привязываем арматуру к «суставам», и через часа три разбирательств, почему все работает не так, как должно, оно все-таки заработало. Я сделал четыре позы, и теперь каждому кружочку (игроку) можно указывать в какой позе он стоит.

Еще нужно будет сделать динамическую смену раскраски формы — это не должно быть сложно. Есть еще идея переносить позу с фотографии — это посложнее, но в целом реалистично. С помощью MediaPipe/AlphaPose можно детектировать ключевые точки в 2D, затем с помощью каких-нибудь моделей типа HMR/HybrIK можно «поднять» плоские координаты в 3D-пространство, выдавая относительные углы поворота суставов. Полученные данные можно попробовать спроецировать на Rigify-скелет. Поскольку пропорции сгенерированной волейболистки и человека на фото могут не совпадать, как раз и используется Inverse Kinematics (IK). Это довольно сложная часть, но в целом она уже не очень обязательная — просто интересно разобраться и сделать что-то работающее.

Видео в комментах

Новогодний сеанс: Avatar в IMAX 3D | 2026-01-01T17:24:09

Как мы встретили новый год? В пустом кинотеатре на Avatar IMAX 3D.

CGI просто потрясающий. Серьёзно, это, возможно, самый фотореалистичный фильм в истории с точки зрения компьютерной графики. Детализация отдельных лиц — временами я готов был поклясться, что смотрю на человеческие лица, покрытые краской (и это комплимент). И ещё там очень много всего происходит на заднем фоне. Или нужно на IMAX и желательно в 3D, потому что это один из немногих фильмов, где технология imax 3D используется не в отдельных сценах, а вообще везде.

Главная злодейка Varang там просто потрясающая. Каждый раз, когда она появлялась в кадре, она перетягивала внимание на себя. Несмотря на CGI, они идеально передали все сложные эмоции её персонажа. Её сделали реально беспощадной, сексуальной и опасной. Клёво получилось.

Хронометраж в три часа очень густо наполнен экшеном, там практически нет сцен, где хочется позевать.

19 лет вместе: ирония и удивление в LiveJournal | 2025-12-24T10:03:29

Офигеть, они реально думают, что «мы» 19 лет вместе и для меня это new achievement.

Создание редактора волейбольных схем: новые технологии для тренеров | 2025-12-23T21:39:02

Завтра вылет в Коста-Рику, а я тут для Нади делаю (или сделал) редактор волейбольных схем. Она как тренер готовится к занятиям, и оставляет после себя сотни страниц текста со схемами на каждой странице. Текст рукописный, и теоретически его просто перевести в электронную форму, а вот схемы в качественную векторную форму переводить замучаешься, их очень много. И я решил сделать софт вчера. И вот сегодня уже первая ласточка, можно пользоваться. Это редактор схем, немного похожий отдаленно на редактор диаграмм. Заодно поразбирался с фреймворком fabric.

Процесс выглядит так. Gemini/ChatGPT через API могут конвертировать рукописные схемы в структуру, которая понимает моя программа. Далее открываем этот файл в программе, и немного подправляем если надо. А может и вообще рисуем заново — для простых схем это даже проще. Там есть четыре типа объекта — игрок, конус, мишень, текст. Любые можно соединять друг с другом стрелками, простыми или пунктирными, подписанными текстом или номером или нет, выбранного цвета, прямыми или по дуге. Если зацепить мышкой за объект, то потянутся за ним все стрелки.

Результат можно записать в файл. Можно открыть шаблон и на его основе сделать что-то новое. Можно сгенерировать скрипт на питоне — вчера это было еще актуально, сегодня в целом не надо уже — SVG/PNG высокого разрешения делаются сразу из этого приложения (вчера делались отдельно с питона).

Понятно, почему сразу не попросить Gemini/ChatGPT сделать что-то для готовых векторных редакторов: во-первых, они слишком гибкие и ограничить фантазию LLM довольно сложно. В итоге получаются разностильные, никуда не годящиеся картинки. Тут же есть фреймворк из четырех объектов и все, LLM о нем знает и генерит только то, что им можно отобразить. Во-вторых, этот фреймворк оперирует объектами, а не элементарными векторными примитивами.

В целом, это первый шаг к моей идее про систему автоматического диаграммрования по описанию. Когда даешь LLM описание диаграммы, а она консистентно генерит то, что написано в описании, и если ты что-то подправил, то при перегенерации это изменение будет учитываться.

Футуристический парадокс: дешевый труд против дорогих роботов | 2025-12-21T15:27:23

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

Еда и одежда стремительно обесцениваются. Объемы производства такие, что накормить и одеть «кожаный мешок» сегодня стоит копейки. А теперь сравните это со стоимостью разработки, софта и техобслуживания сложного робота-официанта или клинера. Человек — это саморегулирующаяся система, которая сама себя заправляет и обновляет. И легко заменяется, если износилась. Чистая экономия!

В «Первом мире» исчезнет мотивация вкалывать. Зачем идти на тяжелую, скучную работу, если базовые потребности закрываются минимальными усилиями, а все остальное — другими людьми, кому это реально надо? Люди в развитых странах будут работать только там, где есть драйв и удовольствие. В итоге мы получим дефицит рук там, где «не прикольно», но роботов там всё равно не будет — дорого.

А вот бедные страны застрянут в прошлом. В них ещё и население растет как на дрожжах. Выбор работы там — это роскошь, доступная единицам. Избыток рабочих рук делает труд практически бесплатным.

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

А вот с бедными будет сложнее. Зачем изобретать сложного робота, если можно перенести завод туда, где тысячи людей готовы работать за еду, которая с каждым годом только дешевеет? Так давно уже происходит, и скорее всего будет ещё долго происходить.

Условно программистов в США заменят не AI, а программисты из Юго-Восточной Азии и Южной Америки. Над ними поставят несколько слоёв AI для контроля качества и одного менеджера, подтверждающего выводы AI и автоматические увольнения и наборы. А те программисты, что ещё останутся в развитых странах, будут больше про оркестрацию , чем про кодинг. Для этой роли мозги нужны ещё больше, и будет способен только один из текущих десяти. Только причиной такого кризиса будет не AI.

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

Будущее — это не восстание машин. Это когда одни работают в кайф, а другие — потому что они дешевле электричества и железок.

Согласны или я слишком сгущаю краски?

Робомассаж Aescape: массаж будущего? | 2025-12-19T21:26:58

Сходили с Надей на робомассаж Aescape. Ну так, мне интересно было посмотреть на техническую сторону всего этого. В целом довольно интересно, но ехать 45 минут на машине вместо 15 и получать робота, пусть даже чуть дешевле.. ну так.. не уверен, что имеет смысл ходить туда регулярно. Другое дело, если ты уже там в зале занимаешься, и хочешь массаж прямо сейчас, без записи — это такой заменитель массажного кресла «на максималках». Да, в этом случае прям самое то.

Система сканирует тело четырьмя камерами под потолком, строит 3D-модель, и дальше в целом довольно неплохо эти роборуки отрабатывают, погружаясь в мышцы ровно так, как надо, где-то посильнее, где-то послабее — с учетом анатомии вообще, и конкретного массажируемого на столе. Кто-то может сказать, а не убьют ли они нафиг из-за какого-то бага, но мы и туда, и обратно ехали на автопилоте Теслы, и уж если машины решили бы нас убить, у них был бы шанс попроще.

Перевод Excel-организма в код: стратегия и исполнение | 2025-12-17T18:56:17

Все мы с этим сталкивались — «Главная Excel-Таблица, Управляющая Бизнесом». Та самая, которую B2B-компании используют, чтобы считать котировки на миллионы долларов. В ней 12 вкладок, 1000+ вложенных формул и ноль документации. Десять лет туда лепили «быстрые фиксы» и прятали константы. Это уже не файл, а живой организм, который уже никто до конца не понимает кроме того чела, уволившегося годы назад. Вот такой я был озадачен. Более того, там еще была неопределенность нужна ли вообще половина формул, или это рудименты прошлого.

Типичная ячейка:

=IF($D11=$D10,»», IF(ISNUMBER( INDEX(Data!$T$10:$U$17,

MATCH(TabCalc!$F11,Data!$T$10:$T$17,0),2)),

INDEX(Data!$T$10:$U$17, MATCH(TabCalc!$F11,Data!$T$10:$T$17,0),2),

INDEX(TabProd!$C$8:$U$112,TabCalc!$D11,I$1)))

Мне поручили перенести эту логику в код, чтобы все считалось софтом. Excel-файл как бы все имел что надо, но по факту — это был сложнораспутываемый черный ящик. 1069 формул.

Челлендж был в том, как перевести тысячу взаимозависимых формул в чистый код и не потерять ни одного пограничного случая (edge case).

В итоге вот что я сделал.

Вместо того чтобы переписывать всё с нуля одним махом с неопределенными перспективыми наплодить багов, я использовал стратегию ленивых вычислений и моков.

Я построил структуру на Groovy, которая имитировала поведение Экселя. Каждое вычисление (из ячейки) я определил как функцию, которая выполняется только тогда, когда её вызывают. А функциями был многомерный dictionary.

Я пошел с конца графа вычислений: от результатов к входным данным. Если формула зависела от чего-то, что я еще не написал, я «мокал» это в коде, просто подставляя значение из Excel-листа.

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

Другими словами, движение шло от результата к исходным данным. На каждом шаге было ясно, какие моки надо превратить в код, и можно было сравнить версию +1 с версией -1 — результат должен был совпадать. Как только все моки заменились на вызовы — задача была готова.

Настоящим «секретным ингредиентом» стала динамическая природа Groovy для создания многомерной карты функций. Вместо статических переменных я использовал глубоко вложенную структуру, где каждый «лист» был замыканием (closure). Это позволило обращаться к любой части таблицы — будь то входной параметр, константа конфига или сложный промежуточный результат — через простой, унифицированный синтаксис, причем некоторые компоненты были динамическими.

Вот пример:

conf[«group»] = { x -> [«a», «b», «c»] }

conf[«group»]().each {

calculate[«Group»][«Subgroup»][it][«TotalQuantity»] =

{

x -> calculate[«Group»][«Subgroup»][it][«Someparameter»]() * conf[«someConstant»]()

}

}

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

Тестировать можно было прямо сразу после начала переноса формул. Прелесть была в том, что ты вроде как как бы к ячейке обращаешься через синтаксис типа calculate[«Totals»][«A»](), а на самом деле запускаешь целое дерево вычислений в этот момент. И это дико удобно в отладке.

Через две недели «Черный ящик» превратился в прозрачную, модульную библиотеку с понятной логикой, которая выдавала ровно тот же результат, что и оригинальная таблица.

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

Разгадывая лабиринт запросов: как я превратил хаос в четкий код | 2025-12-17T03:25:38

Ух какую я прикольную задачку только что решил. Хрен только объяснишь. Ну я попробую.

Короче, у клиента есть 10 сайтов с поиском. Они все используют один индекс, но кидают разные запросы к нему. К тому, что вводит пользователь, прибавляется очень длинный и сложный query, который генерирует модуль на сайткоре. Он содержит айдишники шаблонов и страниц, которые нужно включать или исключать. В итоге понять, что там происходит, вообще невозможно. Там может быть десять открывающих скобок и где-то рандомно закрывающие, но с Coveo работало. Реформаттинг помогал, но не сильно.

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

В итоге вот что я сделал:

Написал скрипт, который разбирает эту текстовую «кашу» в абстрактное синтаксическое дерево (AST). Это позволило превратить нечитаемую строку в структурированный JSON-объект, где четко видно: вот тут AND, тут OR, а тут — конкретное условие.

Дальше я превратил эти условия в формулы булевой алгебры. С помощью библиотеки SymPy я «скормил» эти формулы алгоритмам упрощения. Математика сама выкинула дубликаты, схлопнула лишние вложенности и убрала условия, которые логически поглощаются другими. В результате «деревья» стали плоскими и понятными.

В аттаче — оригинальное дерево и упрощенное.

Чтобы быть уверенным, что я ничего не сломал при упрощении, я написал генератор тестов. Он берет упрощенную логику, собирает её обратно в рабочий curl и проверяет, совпадает ли количество найденных документов (totalCount) с оригинальным запросом. Цифры сошлись — значит, логика сохранена на 100%.

Имея на руках упрощенные и стандартизированные структуры для каждого сайта, я построил матрицу сравнения. Скрипт проанализировал их и выделил Common Core — условия, которые гарантированно требуются (или запрещены) на всех сайтах без исключения, и Specifics — уникальные «хвосты», которые отличают один сайт от другого.

На приложенном скриншоте: REQ означает, что условие гарантированно выполняется для любого документа, который пройдет через этот запрос. NOT — гарантированно не выполняется. OPT — условие присутствует в запросе, но оно не является строгим само по себе. Оно работает только в связке с чем-то еще. «.» — условие вообще не упоминается в запросе.

Для 3 сайтов моментально отвечает, для 10 работает минут 30.

Ну и конечно, все данные на всех скриншотах тщательно обфусцированы.

Каждое дерево на карте: Открытые данные графства | 2025-12-15T15:40:16

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