Последние рекорды серверов
Pro Nub

Физика Cliptype

Опубликовано Kpoluk 6 Авг 2023 в 11:04
Погрузимся в тему компиляции карт чуть глубже. Для компиляции повсеместно используются zhlt (Zoner's Half-Life Tools) и его более новая доработанная версия vhlt (Vluzacn's Half-Life Tools). Эти компиляторы состоят из четырёх утилит, которые запускаются последовательно:

hlcsg.exe - по информации из *.map файла карты, в котором лаконично записана информация об entity, брашах и используемых текстурах, подготавливает секции, связанные со структурой карты (двоичные деревья, плоскости, грани, вершины, рёбра, связи между ними), получая на выходе предварительную версию *.bsp файла (в игре его пока запустить нельзя), а также ряд временных файлов для последующей работы остальных утилит (для hlbsp файлы *.p0/1/2/3 с информацией о плоскостях, файлы *.b0/1/2/3 со вспомогательной информацией по брашам, файл *.hsz с размерами оболочек, для hlrad *.wa_ файл c информацией о текстурах и др.).

hlbsp.exe - на основе полученных от hlcsg файлов производит проверку утечек, доразбивает или производит слияние граней, удаляет лишние узлы деревьев и на выходе даёт *.bsp файл, который уже можно запустить в игре, а также временные файлы (*.ext для hlrad и *.prt для hlvis с информацией о порталах, которых в CS впрочем нет, в *.lin и *.pts записывается информация об утечках).

hlvis.exe - разбивает всю карту на зоны, которые позволяют исключать из рендера области, заведомо не попадающие в поле зрения игрока, тем самым улучшив производительность (критично для больших карт). Полученная информация заносится в секцию LUMP_VISIBILITY файла *.bsp.

hlrad.exe - просчитывает освещение, занося данные в секцию LUMP_LIGHTING файла *.bsp.

По ходу работы утилит информация о ходе выполнения операций записывается в *.log файл, а в случае возникновения ошибок создаётся *.err файл, в котором можно узнать, что именно пошло не так. При этом работу утилит можно настраивать с помощью многочисленных ключей запуска, которые могут значительно влиять на структуру брашей. В частности, ключ -cliptype утилиты hlcsg помимо прочего определяет то, каким образом будут сдвинуты плоскости clip-деревьев относительно плоскостей основного дерева, и сопровождается одним из следующих параметров: smallest, normalized, simple, precise, legacy.


Simple и Legacy


Про сдвиги вертикальных и горизонтальных плоскостей мы уже говорили, но вот для наклонных поверхностей возможны несколько вариантов. Посмотрим на код hlcsg компилятора vhlt, для простоты взяв в качеcтве оболочки hull стоячего игрока и подставив значения констант:


Начнём с варианта 1, работающего для типов simple и smallest, как самого интуитивно понятного. Здесь для наклонных плоскостей мы просто комбинируем горизонтальный и вертикальный сдвиги:


Заметьте, что если в таком варианте при переходе с горизонтальной поверхности на наклонную смотреть вниз, то можно увидеть, как расстояние до земли ощутимо меняется. Судя по всему, создателям компилятора этот эффект не понравился, и они добавили вариант 2, работающий для типов legacy и normalized. Для него сдвиг clip-плоскостей зависит от направления нормали, то есть фактически от наклона поверхности. Пример подъёма под 45° даёт в варианте 2 следующие сдвиги:


Оболочка стоячего игрока оказывается утоплена в видимом браше на (36 - 36 * cos(45°)) + (16 - 16 * cos(45°)) = 15.23 юнита по горизонтали. Соответственно расстояние от центра нижней грани параллелепипеда игрока до наклонной плоскости составляет всего 0.77 юнита по горизонтали и вертикали. При этом для сидячего игрока параллелепипед будет утоплен на (18 - 18 * cos(45°)) + (16 - 16 * cos(45°)) = 9.96 юнита по горизонтали, а расстояние от центра нижней грани до земли составит 6.04 юнита, то есть на наклонной поверхности разность между центром игрока в сидячем и стоячем положениях оказывается меньше 18 юнитов. Как бы то ни было, для типа legacy визуально переход на наклонную поверхность меняет расстояние до земли значительно меньше, а значит успех, казалось бы, достигнут, но тут начинают появляться нюансы.

Взглянем на тестовый проект hull_test.jmf и две скомпилированные из него карты hull_simple.bsp и hull_legacy.bsp (файлы проекта и карты можно скачать здесь):


На первый взгляд карта содержит два ряда идентичных объектов, и на hull_simple.bsp, скомпилированной с типом simple, никакой разницы между ними вы действительно не заметите. А вот на hull_legacy.bsp, скомпилированной с типом legacy, объекты в переднем ряду, состоящие из одного браша, существенно отличаются от объектов позади, состоящих из пары брашей. Начнём с уже знакомой платформы с подъёмом под 45°:


Разделение на два браша приводит к тому, что платформа в заднем ряду получает дополнительную плоскость clip-нода:


Как результат в конце подъёма нас ждёт невидимая ступенька высотой 15.23 юнита. Из статьи про физику EdgeBug и JumpBug мы уже знаем, что при прохождении ступеньки не выше 18 юнитов движок просто подбросит нас на нужную высоту. Именно этот скачок мы можем наблюдать и на нашей платформе, что особенно хорошо заметно при медленном подъёме.

Далее на той же hull_legacy.bsp опробуем клиновидные браши в стене слева. На виде сверху они выглядят следующим образом:


Плоскости, расположенные под 45° к стене, получили сдвиги по X и Y (одинаковые для стоячей и сидячей модельки), причём клин в задней части комнаты, состоящий из двух брашей, также имеет дополнительную плоскость, в которую мы упрёмся, попытавшись двигаться вплотную к клину. Ступенькой этот невидимый выступ не является, так как лежит в другой плоскости, поэтому движок нам с его прохождением никак не помогает.

Следующие на очереди блоки с наклонной поверхностью наверху:


Высота нижней части этих блоков равна 64 юнитам, поэтому на версии hull_simple.bsp ни на один из них запрыгнуть было нельзя, однако на hull_legacy.bsp блок в ближнем ряду благодаря сдвигу наклонной плоскости оказывается для сидячей модельки выше не на 18 юнитов, а всего лишь на уже знакомые нам 6.04 юнита:


В то же время состоящий из двух брашей блок в заднем ряду имеет дополнительную горизонтальную плоскость, которая делает его неприступным:


Правее стоят блоки со слайдом:


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

Тип legacy является самым рапространённым среди карт в CS 1.6, включая стандартные карты. Связано это скорее всего с тем, что он использовался компилятором zhlt по умолчанию. При этом vhlt по умолчанию использует тип simple, однако игроки уже так привыкли к физике legacy, что большинство мапперов вручную добавляют -cliptype legacy в ключи запуска hlcsg.


Precise


Хотя примеров использования типа precise не так много (из известных карт можно назвать hfr_anoyo и hfr_unshape), пару слов про него сказать стоит. В приведённом ранее отрывке кода часть под #ifdef HLCSG_CLIPTYPEPRECISE_EPSILON_FIX появилась в vhlt, в то время как zhlt использовал для precise старый вариант, фактически совпадающий с legacy для наклонных поверхностей круче 45° и с simple во всех остальных случаях. В коде также можно найти комментарии, указывающие на недостатки такого подхода, в частности на проблемы с отступом в eps, который мы обсуждали в статье про физику pixelwalk. В связи с этим vhlt для типа precise в случае наклонных плоскостей, являющихся слайдами (то есть с углом наклона больше acrcos(0.7) = 45.573°), делает стандартный сдвиг на 18 или 36 юнитов по вертикали, при этом по горизонтали не делая сдвига вообще; для всех остальных плоскостей сдвиг аналогичен типу simple.


Максимальная высота блока


Начнём с простого. Пусть перед нами расположен куб со стороной 63 юнита. Из статьи про физику bhop мы знаем, что расчётная высота прыжка с места равна 45 юнитам. Прибавляем сюда 18 за счёт приседания, и получаем как раз 63 юнита. Однако в действительности моделька игрока перемещается дискретно, поэтому 45 юнитов мы на самом деле не достигаем. Если обозначить как V0 начальную вертикальную скорость после прыжка и вспомнить, что каждый фрейм эта скорость уменьшается на 8 юнитов/с, то получится, что максимальной высоты h мы достигнем спустя N = [V0 / 8] фреймов (квадратные скобки здесь означают целую часть числа). Далее считаем:

h = V0 * 0.01 + (V0 - 8) * 0.01 + (V0 - 8 * 2) * 0.01 + ... + (V0 - N * 8) * 0.01
h = V0 * (N + 1) * 0.01 - 0.01 * 8 * (1 + 2 + ... + N)
h = 0.01 * (N + 1) * (V0 - 4 * N)

Из той же статьи про физику bhop вспоминаем, что начальная вертикальная скорость равна sqrt(2 * 800 * 45) = 268.3281573 юнита/с, плюс не забываем, что на момент трейса PM_FixupGravityVelocity успевает отнять 4 юнита/с, итого V0 = 264.3281573 юнита/с. Отсюда N = 33 и h = 44.991573 юнита. Выходит, что с такой высотой прыжка мы до 63 юнитов не дотянем. Почему же у нас получается запрыгивать на такую высоту?

И вот тут к нам на помощью приходит отступ eps = 0.03125 юнита из статьи про физику pixelwalk. Этот зазор между моделькой игрока и уровнем пола даёт недостающие сотые доли и позволяет превысить высоту куба в 63 юнита на 0.022823 юнита. Правда, в таком случае после запрыгивания на куб зазор между моделькой и верхом куба останется равным именно 0.022823, а не 0.03125. Это значит, что если мы попытаемся с этого куба запрыгнуть на ещё один такой же, то запаса по высоте уже будет меньше, и новый зазор тоже станет меньше. После третьего такого куба подряд зазор практически исчезнет, а на четвёртый куб высоты прыжка уже не хватит. Чтобы сбросить зазор до eps, достаточно сделать после третьего куба doubleduck или обычный прыжок. Такой эффект является прямым следствием работы функции PM_RecursiveHullCheck, которую мы разбирали в статье про физику pixelwalk: если начальная точка трейса оказалась к плоскости браша ближе, чем на eps, то подобраться ещё ближе функция ей не даст, но и отодвигать назад на границу eps-области также не будет.

Двигаемся дальше. На примере карты hull_legacy.bsp мы увидели, что тип legacy благодаря наклонным поверхностям значительно влияет на высоту блока, на который можно запрыгнуть. К примеру, плоскость под 45° дала нам запас в 9.96 юнита. Значит, нижнюю часть блока можно было поднять до 72 юнитов, а до 73 на первый взгляд не хватает даже с учётом зазора eps между моделькой игрока и полом. Но на самом деле при определённых условиях запрыгнуть на эту высоту всё же можно:


Нарисовав розовые eps-области вокруг clip-нодов, мы обнаруживаем себя в ситуации, аналогичной трейсу для pixelwalk. Разве что пересечение eps-областей даёт здесь не красный квадрат, а красный ромб, и благодаря этому трейс из начальной точки p1, не дотягивающей до уровня 73 + 18 юнитов (18 прибавляем потому, что говорим о взаимодействии центра игрока с clip-нодами для сидячей модельки), всё же пересекает и вертикальную, и наклонную плоскости. Ну а дальше уже знакомая нам ситуация - если clip-нод с вертикальной плоскостью встретился при обходе clip-дерева раньше, чем clip-нод с наклонной плоскостью, то в результирующий трейс запишется наклонная поверхность, а значит запрыгнуть у нас получится.

Для демонстрации этого явления я создал карту hull_73_legacy.bsp типа legacy (скачать можно здесь), на которой расположил браш высотой 74 юнита и сделал два скоса под 45° высотой в 1 юнит на двух противоположных рёбрах:


На рисунке подписаны индексы clip-нодов, соответствующих плоскостям блока. Полностью clip-дерево карты для сидячего игрока выглядит следующим образом:


Как видим, clip-нод #36 при обходе дерева встретится позже, чем #33, поэтому запрыгнуть на блок спереди у нас получится. Кроме того, clip-нод #37 встретится позже, чем #34, а значит с задней стороны на блок не запрыгнуть.

А как дела в подобной ситуации обстоят у типа simple? Конечно, настолько же высоко, как на legacy, запрыгнуть не выйдет, но высота в 63 юнита ведь должна браться? Оказывается, здесь тоже не всё так очевидно. Посмотрим на тестовую карту hull_63_simple.bsp c cliptype типа simple (скачать можно здесь). На ней я создал браш высотой 64 юнита и сделал точно такие же скосы, как в предыдущем примере. В итоге я получил схожее clip-деверо, и спереди на блок можно запрыгнуть без проблем, а вот с задней стороны... Казалось бы, начало трейса здесь, как и в предыдущем примере, попадает в красный ромб на пересечении eps-областей, и порядок clip-нодов в дереве указывает нам на то, что запрыгнуть не получится. Однако на самом деле у нас есть два способа обойти это. Поскольку в верхней точке прыжка мы находимся на высоте выше 63 + 18 юнитов, то во-первых мы можем разогнаться и прыгнуть в сторону блока, надеясь на то, что благодаря горизонтальной скорости ни одна точка трейса не попадёт в eps-область, и мы просто проскочим её, так что следующий трейс встретит только наклонную поверхность:


Другой способ – подойти вплотную к блоку лицом к нему, прыгнуть с зажатой W, а затем в процессе прыжка зажать ещё и D либо A. Такая нехитрая манипуляция позволяет за счёт работы функции PM_AirAccelerate (мы разбирали её в статье про физику стрейфов) уменьшить составляющую скорости в направлении блока настолько, что в какой-то момент конечная точка трейса оказывается внутри eps-области, а значит пересечения плоскости clip-нода нет, и мы продвигаемся ближе к блоку. Таким образом мы постепенно проходим сквозь eps-область и в верхней точке прыжка переходим за её границы:


Собственно говоря, именно с такого блока начинается знаменитая карта holy_lame. А это, кстати, сразу подсказывает нам, что при компиляции использовался cliptype типа simple.

Конец?