KranX / Vangers

The video game that combines elements of the racing and role-playing genres.
https://store.steampowered.com/app/264080/Vangers/
GNU General Public License v3.0
671 stars 90 forks source link

Delta в игре понимается тремя взаимно противоречивыми способами. #609

Open DileSoft opened 2 years ago

DileSoft commented 2 years ago

Сюрмап понимает дельту как толщину верхнего слоя, и, судя по всему, так и предполагалось изначально:

https://github.com/KranX/Vangers/blob/5a214b175fc29dfa750b1c1e71cbbd81aa1d00bb/src/terra/render.h#L109-L118

При рендеринге в 3d провода выглядят нормально:

image

Физика расчета пуль понимает дельту как высоту пещеры:

https://github.com/KranX/Vangers/blob/5a214b175fc29dfa750b1c1e71cbbd81aa1d00bb/src/units/hobj.cpp#L1555-L1564

При рендеринге в 3d провода вытянуты вниз:

image

Физика расчета 3d объектов вообще игнорирует дельту и использует фиксированную ширину верхнего слоя:

https://github.com/KranX/Vangers/blob/5a214b175fc29dfa750b1c1e71cbbd81aa1d00bb/src/3d/dynamics.cpp#L67 Визуализации в 3d третьего способа нет.

kvark commented 2 years ago

Давайте рассудим всё основательно. Разделим формальные признаки (сам код) от неформальных (все остальные соображения).

неформальные

Что было бы логичнее? С точки зрения наименования "delta" - это больше похоже на расстояние между двумя пластами земли, она же высота пещеры.

Что говорят авторы? По сообщению Андрея Кузьмина, была проблема:

я только помню, что с этими потолками была проблема, задевали крупные мехосы и в целом у меня отложилось подозрение, вто с ними что-то не так, но времени уже не было тогда разбираться. возможно, там как раз баг. круто если он будет наконец исправлен! :)

По сообщению Александра Котляра, это толщина:

толщина верхнего слоя конечно. По нижнему мы ездим, а толщина потолка практически нигде не фигурирует.

Как смотрится лучше? Похоже, что с толщиной смотрится лучше.

Результат - смешаный.

формальные

Давайте покопаемся в коде внимательно:

1) Начнём с Surmap, функция WORLD_handle()

                hd = *pa;
                hu = *(pa + 1);
                if((h = hu - hd) < 32){
                    SET_DELTA(*pf,0);
                    SET_DELTA(*(pf + 1),0);
                    }
                else {
                    h -= 16;
                    if(h > MAX_RDELTA) h = MAX_RDELTA;
                    h = (h >> DELTA_SHIFT) - 1;
                    SET_DELTA(*pf,(h & 12) >> 2);
                    SET_DELTA(*(pf + 1),(h & 3));
                    }
                }

Здесь описана следующая логика: если расстояние между верхним и нижним слоем меньше 32, отнять 16 и закодировать её в дельте. Это похоже именно на высоту пещеры.

2) Продолжим с Surmap, функция AcceptSecondLayer

                h = GET_UP_ALT(pf,*pa,pa0,x);
                hy = GET_UP_ALT(pfy,*pay,pay0,x);
                d = abs(h - hy);
                if(d >= TunnelHeight){ ...
                    d = (d >> DELTA_SHIFT) - 1;
                                        ...
                            SET_DELTA(*pf,(d & 12) >> 2);
                            SET_DELTA(*(pf + 1),(d & 3));
                                         ...
                                 }

Опять же, разница между уровнями записывается в d, сравнивается с некой константой TunnelHeight (практичекси дословно "высота пещеры"), а потом записывается в нашу дельту. Так что тут совершенно точно высота пещеры.

3) подземные эффекты. Например, MakeSecondLevel:

int d = 32;

Похоже, во всём модуле полагается дельта равная 32. К какой семантике больше подходит? Не ясно.

4) Неподалёку лежит файл "hobj.cpp" с функцией GetCollisionMap:

                d = *(p -1);
                if(d < z && (d + (((GET_DELTA(*(t - 1)) << 2) + GET_DELTA(*t) + 1) << DELTA_SHIFT)) > z) return 0;

Тут вычисляется пересечение точки с ладшафтом. Совершенно ясно, что берётся трактовка дельты как высоты пещеры.

5) идём дальше - в код модификации земли, pixDownSet. В модуле есть много упоминаний от том, как дельту складывают с нижним слоем:

if((int)*(pa - 1) + (((GET_DELTA(*(pf - 1)) << 2) + GET_DELTA(*pf) + 1) << DELTA_SHIFT) >= h){

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

6) переходим к 3Д физике в файле "dynamics.cpp". Интересное объявление:

#define GET_THICKNESS(p) ((GET_DELTA(*HIGH_LEVEL((p) + H_SIZE)) + (GET_DELTA(*LOW_LEVEL((p) + H_SIZE)) << 2) + 0) << DELTA_SHIFT)

Вычисляется всё та же дельта, но результат называют "thickness", что могло бы означать толщину верхнего слоя. Но анализ реального использования этого макроса говорит о другом. Вот get_three_heights:

    uintptr_t ll = *LOW_LEVEL(p);
    return ll | (ll + (GET_THICKNESS(p) << 8)) | ((uintptr_t)(*HIGH_LEVEL(p)) << 16) | 0xff000000;

Функция возвращает 3 отметки высоты: первый уровень, начало второго уровня, конец второго уровня. Явно видно, что "thickness" не вычитается из второго уровня, а прибавляется к первому. Это значит, что настоящий смысл данного макроса - вернуть высоту пещеры.

Кроме того, это похоже на реальный баг. Смотри #610 с предлагаемым исправлением.

7) Последняя остановка нашего путешествия (а я полагаю, что ничего не пропустил) - код отрисовки "render.h". Начнём с до боли знакомой обёртки GET_WIDTH:

inline uchar GET_WIDTH(uchar* type, int x)
{
    if(*type & DOUBLE_LEVEL){
        if(x & 1)
            return (((GET_DELTA(*(type - 1)) << 2) + GET_DELTA(*type) + 1) << DELTA_SHIFT);
        else
            return (((GET_DELTA(*type) << 2) + GET_DELTA(*(type + 1)) + 1) << DELTA_SHIFT);
        }
    return 0;
}

Итак, она возвращает нашу закодированную дельту, также как и GET_THICKNESS в другом месте. Любопытно, что она используется только в одном месте - отладочном (нефункциональном) коде surmap:

t = GETWIDTH(CX,CY);
        status < "x:" <= CX < " y:" <= CY < " col: " <= *(vMap -> lineTcolor[CY] + CX);
        status < " type: " <= GETTERRAIN(CX,CY);
        if(dl) status < "/" <= GETDOWNTERRAIN(CX,CY);
        if(!dl)
            status < " alt:"<= u;
        else {
            d = GETDOWNALT(CX,CY);
            status < " d:" <= d;
            status < " u:"<= u;
            status < " t:" <= t;
            status < " w:" <= (u - d - t);
        }

Отсюда не ясно, что оно значит. Более того, похоже, что значение менялось. GETWIDTH присваивается переменной t, которая вероятно изначально принимала "thickness", а потом вычисляется некий "w:" на основе этого (вероятно, "width").

Есть ещё такая штука в этом же файле:

const int DELTA = 16;

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

8) как же всё-таки рисуется? Полагаю, дельта для отрисовки не используется вовсе. Просто рисуется нижний и верхний слои. Если кто-то сможет раскопать тут по-лучше - буду рад помощи. Но в такой интерпретации это и не глубина пещеры, и не толщина слоя.

итоги

Неформальные доводы смешаны. Формальные доводы в виде кода оказались практически непротиворечивыми - из всех 8-ми мест использования дельты, 5 оказались в значении высота пещеры и ни одного, конкретно указывающего на толщину.

В коде явно что-то менялось, и вполне возможно, что раньше кодировалась именно "thickness". Возможно, Котляр этот момент и вспомнил. Но в той версии, что у нас есть сейчас, всё прямо указывает на высоту пещеры, и логика surmap в этом согласна с 3Д физикой и всем остальным. На случайную ошибку не похоже.

DileSoft commented 2 years ago

У меня есть возражения.

Во-первых, я исхожу из того, что игру пишут не ради кода, а ради конечного результата. Соответственно, если мир выглядит искаженным, то это явно неправильное понимание кода. Тут есть и чисто практическая причина. Очевидно, когда разработчики рисовали мир, они рисовали провода прямымим, проемы под мостами широкие и т. д. В том числе просто вставляя 3d-модели в карты высот. Предположить что они во всех мирах везде одинаково ошиблись, маловероятно. Поэтому очевидно, что миры рисовались исходя из того, что delta это толщина верхнего слоя.

Тени, которые (хотя это надо бы проверить) рисуются исходя и из ширины, и из толщины верхнего слоя, показывают провода прямыми. Если бы они рисовались исходя из дельты-пещеры, они бы были вытянутыми.

Во-вторых, как я понял, ты просто почитал код, а я проверял, для чего он используется и где выполняется. Теперь подробнее:

  1. Здесь я скорее согласен что высота. Надо бы проверить, вызывается ли этот код.
  2. А точно h и hy это высота верхнего и нижнего слоя? Надо бы проверить, вызывается ли этот код.
  3. Да, непонятно.
  4. Обсуждал в первом комментарии. Используется только для пуль.
  5. Да, непонятно.
  6. get_three_heights при выполнении игры никогда не вызывается. Можно даже увидеть почему.
  7. Это функциональный код. Он выполняется для того чтобы сверху сюрмапа нарисовать свойства текущей точки поэтому вычисляется t, а затем w исходя из t. Причем вычисляется по формуле так, будто t из дельты это толщина, а w высота пещеры.

Я согласен с тем, что код противоречив. Но визуально все выглядит так, что когда рисовали миры, дельту считали толщиной. Дальше действительно непонятно что происходило.

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

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

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

DileSoft commented 2 years ago

Попробовал на потрошители проехать под проводом на берегу и заехать в воду.

Мехос наткнулся на препятствие и наклонился вниз как будто провод реально вытянулся вниз.

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

image

Короче физика у нас противоречит логике.

Проверил еще в сюрмапе.

Начал наращивать верхний слой. w выросло, t осталось на месте.

Начал наращивать нижний слой. t и w в процессе попеременно то увеличиваются то уменьшаются.

Но судя по верхнему слою, t реально высота пещеры, а w наоборот толщина верхнего слоя.

Получается, ты прав и сюрмап тоже так считает.

Я признаю, что везде сейчас дельта это высота пещеры.

Но это приводит и к кривому визуалу с вытягивающимися проводами и толстыми мостами, и к кривой физике, где ты натыкаешься на вытянутые провода над рекой.

Короче, дельта = пещера это канонично, но выглядит и играется неправильно.

DileSoft commented 2 years ago

Остался правда вопрос с тенями. Но как они считаются, понять невозможно.