Так, брашевые извращения, походу отпадают. Оно работает и коллизия гладкая, но занимает чертовски много места, просто охренеть сколько.
Вес плоскостей может в 2-3 раза превышать вес видимой геометрии.
Конечно для коллизии геометрию можно и нужно симплифицировать, но всё равно это не выход. Для кордона эти плоскости уже весят сопоставимо со всем остальным. И это только для террайна и статик-моделей.
Причём скомпилить карту, при использовании полноценных брашей (не односторонних) я уже не могу - памяти не хватает. А если бы хватило, вес бы увеличился еще на столько же. Это просто нерационально.
Смысл любой оптимизации, в том и заключается, что и без нее всё хорошо, а с ней - вообще отлично. Но надеяться на симплификацию, как на разрешение проблемы нельзя. Это её просто слегка отодвинет в сторону.
Только время тратить. Ну хорошо, посмотрим какие у нас есть варианты в трассировке треугольников при помощи ббокса.
Вообще надо заметить, что проверка bbox vs triangle очень тяжёлая. Особенно class III test.
ncuxonaT ну а как игрока представить? Капсулой? Это пока не существенно. Вот кстати, хотел бы с вами посоветоваться насчёт одной коллизии. Описание компиляторных энтить в скриптах - это почти полноценный исполняемый язык, хоть и с некоторыми оговорками.
И в этом языке возникла небольшая коллизия, сейчас поясню на примере.
C++ Source Code:
1
"env_static"
2
{
3
addCompileFlags( C_DETAIL );
4
includeModel( "model" );
5
6
if( spawnflags & 1 )
7
forceClipModel();
8
if( spawnflags & 2 )
9
dropToFloor();
10
11
transformModel( "self" );
12
movePrimitives( "classname", "worldspawn" );
13
14
removeEntity();
15
}
Это описание компиляторной энтити env_static. Надеюсь тут и человеку далёкому от программирования понятно, как это устроено и как работает.
Это именно выполняемый код, который исполняется так же как и любой другой. Но здесь есть ошибка, привязанная к некоторым особенностям языка. Совершенно неочевидная, к слову, поскольку присуща именно моему язык. Сейчас поясню.
Дело в том что условия
C++ Source Code:
if( spawnflags & 1 )
if( spawnflags & 2 )
будут выполняться не так, как того ожидает пользователь (это касается именно битовых операций, но теоретически возможны и другие коллизии). Почему так происходит?
Всё дело в том, что в этом языке нет средств, позволяющих указать явным образом с каким типом данных мы имеем дело. Интерпретатор скрипта пытается догадаться об этом самостоятельно, следуя нехитрой логике, а именно: каждая константная строка сперва интерпретируется как имя ключа в текущей энтите. Если таковая действительно будет найдена, то на её место будет подставлено значение для этого ключа. Это вполне логично и соответствует ожиданиям пользователя. Далее, если окажется что, значение цифровое, оно будет переведено в цифры. А если нет - то для него будет посчитан хэш, для последующего сравнения строк. Теперь представим ситуацию, что в описании энтити этого поля нет вообще.
Строка spawnflags будет естественно распознана, именно как строка, для нее посчитается хэш (CRC32 если быть точным) и... битовая операция произойдет применительно к этому хэшу. Следовательно результат проверки окажется вообще бессмысленным. Потому что повторюсь, язык не различает, где просто константная строка, а где значение из настроек энтити. Нет такого средства в языке. Правильный вариант условия выглядит следующим образом
C++ Source Code:
1
if( spawnflags )
2
{
3
if( spawnflags & 1 )
4
forceClipModel();
5
if( spawnflags & 2 )
6
dropToFloor();
7
}
Здесь мы уже проверяем что такое поле вообще существует. Причём в данном случае компилятор уже не трактует строчку как константную, он уверен, что это именно ключ в настройках энтити. С другой стороны - я не могу сделать подобного исключения для битовых операций, потому что регистры грузятся вообще без оглядки на сами команды.
Но можно придумать какой-нибудь модификатор подсказку, чтобы скрипт точно знал, что это ключ, а не константное слово. Но с другой стороны - это может испохабить синтаксис. Вы бы как поступили на моём месте?
Дядя Миша писал: Вы бы как поступили на моём месте?
Странная идея с хэшами, ну ладно.
Я бы поступил так:
1) Если поле отсутствует - то оно "", цифровое значение для битовых операций 0.
2) Если поле есть - преобразуем в число. Если число не является целым, или вообще не число - тогда считаем что оно 0. Можно ещё варнинг кинуть.
Там CRC32 checksum, про хэш это я так. Буков меньше писать.
Цитата:
nemyax писал: Но там нет строки "spawnflags"
ну это же всё-таки скрипт. Толерантность. Здесь нельзя объявлять какие-то локальные переменные, и нет никаких типов. Любая строка так или иначе приводится к чексумме, любое число передаётся неизменным.
Вот пример условия
C++ Source Code:
if( targetname == t1 )
Оба операнда можно взять в кавычки, это ни на что не повлияет.
Для обоих выполняется одна и та же раписанная выше операция - сперва каждая строка интерпретируется как ключ. Для targetname это удаётся, и тогда она подменяется содержимым ключа. Для t1 это не удается и она остается первоначальной строкой. Дальше обе строки превращаются в чексуммы (виртуальная машинка работает только с целыми числами). И дальше над двумя регистрами выполняется запрошенная операция, в данном случае сравнение.
Так я же об этом и толкую. Объявлять ничего нельзя. Здесь нет переменных, только обращение. И данные бывают двух типов - либо это содержимое ключа, либо константа. Над ними нельзя производить операции кроме как сравнения или сохранять куда-то полученные результаты. Хотя модифицировать имена ключей и их содержимое - можно. Но - только такими же константами. Или на худой конец - содержимым соседних ключей в пределах одной энтити.
Добавлено 14-01-2021 в 00:39:
Ну да ладно, вернёмся к колоизации. Вообще я думаю, дело даже не только и не столько в методах, сколько в выборе дерева. Здесь по идее мне нужно аксиальное нерегулярное BSP. Которое будет искать оптимальный сплиттер по двум критериям - минимальному размеру ноды и максимальному кол-ву полигонов, которые лежат на ноде. Т.е. отбалансировать это дерево именно для коллизии. Стоковое кутришное дерево для этих задач абсолютно негодится. Возможно я его тоже потом отбалансирую, но это не так критично. Смысл в том, что дерево ничего не должно разрезать, в этом плане уподобляясь AABB-tree, но при этом быть иррегулярным. AABB это как правило регулярная сетка, где каждый узел содержит ровно один полигон (хотя это и необязательно конечно). Из-за чего такое дерево может быть просто гигантских размеров. Ну нам это очевидно не надо.
А подход с несколькими деревьями неплохо себя зарекомендовал еще со времён первой кваки. Для видимости одно дерево, для коллизии другое.
Надо только придумать как потуже сжать клипсурфейсы, у них будет раздельный набор данных - точки и рёбра. Причём рёбра в некотором смысле будут выполнять роль индексов для точек, поскольку каждое ребро содержит индексы двух точек. Ну собсно, как и в халфе.
Так же у каждого ребра есть нормаль, но я думаю хранить их в карте слишком расточительно, лучше заново рассчитать при загрузке.
С этой коллизией, немаловажная задача стоит, определиться какую именно геометрию мы будем использовать. С одной стороны - нам непременно нужна видимая, для точечной трассы, которая способна трассировать тексели, например для пропуска пуль сквозь решётки, ну и не только.
С другой стороны, нам нужна упрощённая геометрия для тестирования примитивов. И вот здесь конечно затык у меня. Хранить оба вида геометрии в карте? Строить точную геометрию при загрузке, а упрощённую считать при компиляции? Оба типа геометрии рассчитать при компиляции?
Всё это так или иначе угрожает переизбытком данных. Конечно не столь масштабным, как если бы вознамерился сконвертить каждый треугольник в браш, но всё равно.
Для начала я решил всё же опробывать аксиальное дерево из третьего дуума. Хорошая штука, но о том, чтобы строить его при загрузке, речи конечно не идёт, на картах типа кордона это может занять десятки минут.
Дядя Миша писал:
Хранить оба вида геометрии в карте? Строить точную геометрию при загрузке, а упрощённую считать при компиляции? Оба типа геометрии рассчитать при компиляции?
А если пошарить куски, которые годятся туда и сюда?
Совсем пашарить не получится, потому что видимая геометрия натурально хранится в RAW-контейнере, куда доступ только по шаблонам. Для коллизии обращаться с такой датой дико неудобно, поэтому для нее создается локальная копия. Ну она не сильно много места занимает.
А вот с симплифицированной версией визуальной геометрии, тут уже посложнее, её придется хранить в явном виде. Но меня больше занимает вопрос, насколько эта геометрия вообще актуальна.
Добавлено 14-01-2021 в 19:33:
Построил я дерево и даже его визуализировал на всякий случай. Долго строится зараза и полигоны к узлам как-то странновато примыкают. Наверное налажал где-то. У этого дерева есть существенный плюс - его очень легко хранить на диске. Потому что нам посути надо сохранить только axial type (если -1 это лиф), дистанцию до плоскости и два указателя на детей. 16 байт на ноду максимум. Лифов вообще нет, как легко догадаться.
А при загрузке просто отфильтровать геометрию в это дерево, это легко и просто делается, там даже ничего резать не надо. То есть в такое дерево можно профильтровать вообще любую геометрию, ну там брашы, полигоны, наконец полигоны из симплифицированой версии. А в карте таскать только само дерево. В этом несомненный плюс конечно.
Но я его еще не тестил на производительность. Хотя с виду - ну дерево как дерево, наверняка быстрее классического в силу аксиальности. И как я понимаю, обладает некоторыми достоинства R-Tree, KD-Tree и BVH вместе взятых. Впрочем BSP это самый общий случай, поэтому оно и способно приобретать черты узкоспециализированных деревьев и в его построении допускаются абсолютно любые приёмы.
Собственно почему придётся завести два дерева. Потому что для двух задач - коллизии и рендеринга нужны совершенно противоположные требования.
То что идёт на отрисовку сгруппировано большими кусками, там дерево нужно достаточно редкое, отдельные узлы могут быть даже размером 1024х1024. Для физики напротив - скопление мелких полигонов в одном месте провоцируют появление дополнительных узлов там. Это противоречие никак не разрешить в одном дереве. Да вообщем-то и не нужно. К слову сказать, те кто делал OverDose этот момент вообще не поняли. Неудивительно что он такой тормозной получился.
Ввёл в компилятор понятие уровень оптимизации.
Там довольно много такого, что могло бы помочь, но каждый раз ждать пока оно сработает, брр. А для финального компила можно и потерпеть.
В основном влияет на уменьшение размера карты и на увеличение фпс.
Всё забываю устроить опрос. У меня есть очень древний вьювер моделек, совмещённый с дециматором. Но главное его преимущество в том, что там аж четыре наиболее известных метода симплификации встроено и можно пощёлкать и оценить качество работы того или иного алгоритма.
В комплекте, разумеется, идёт набор рафинированных моделек, на которых всё гладко. Я недолго думая, при помощи Noesis переконвертил пару моделек из сталкера, в частности, вот камаз и сунул этому вьюверу.
И оставил от первоначального поликаунта примерно 20% (было 9697 трианглов, стало 2288). И сделал пять картинок. Оригинал, чтобы была референсная точка. И четыре различных метода симплификации. И вот мне любопытно, какой из четырёх методов вам покажется наиболее качественным. Оставить 20% от первоначального меша, это довольно непростая задача, если бы я убрал скажем половину полигонов, скорее всего разница была бы малозаметна. Программка называется jmspmesh, если кому-то интересно. Поехали:
Это оригинал:
Quadric Error Metrics (QEM) weighted by triangle area
Quadric Error Metrics unweighted
Original Algorithm by Stan Melax (Стэн Мелакс, это дядька такой известный)
Shortest Edge (не знаю кто автор, но принцип работы можно уместить в одно предложение: выбрасывается самое короткое ребро)