Skip to content

Track and camera

Владислав edited this page Sep 5, 2026 · 1 revision

Трасса и камера

Трасса рисуется построчно: сетка тайлов 16 x 1024 проецируется в 17 полос, каждая - трапеция, у которой известны четыре угла и высота. Ни матриц, ни Z-буфера, ни текстурных координат с плавающей точкой в кадре нет.

Проекция

Одна функция, всё остальное - производное от неё (jb_track.c):

static float Proj(float t)
{
    return (t * JB_PROJ_NUM) / (t * JB_PROJ_DEN_A + JB_PROJ_DEN_B) * JB_PROJ_SCALE;
}

JB_PROJ_NUM 70.0, JB_PROJ_DEN_A 60.0, JB_PROJ_DEN_B 100.0, JB_PROJ_SCALE 163.0 - четыре float из .data (0x0002643c, 0x00026440, 0x00026444, 0x00026438). Гипербола: близкие строки высокие, дальние сжимаются к горизонту, Proj(16) = 172 - это JB_PROJ_SPAN, полная глубина кадра в пикселях.

Track_BuildProjTables() считает её один раз на 29 значений (g_rowProjOfs 0x00061438) и рядом - разности соседних, то есть высоты строк (g_rowHeight 0x000614a8, элемент 1 задан константой 0x96).

Обход строк

Track_DrawFrame идёт от дальней строки к ближней: i от 0 до 17, row = 17 - i. Так близкие полосы перекрывают дальние без сортировки.

y_near = 320.0f - Proj(row     - cam);
y_far  = 320.0f - Proj(row + 1 - cam);
if (i == 0)
    y_far = 320.0f - Proj(row + 1);   /* Track_DrawFrame 0x0001e1c4 */

cam = cam_pixel_ofs / 10 - дробная часть позиции камеры в строках; из-за неё трасса едет плавно, а не прыгает по 10 пикселей. Первая итерация считает дальний край без поправки на камеру - это не описка порта, а поведение оригинала на 0x0001e1c4.

Горизонтальные края

Каждая строка получает четыре X: дальние левый/правый и ближние левый/правый. Считаются они интерполяцией между «полным» масштабом и «дальним»:

FullX(s) = s * tile_size + view_center_x - map_cols * tile_half;  /* 0x0001e2a0 */
FarX(s)  = s * 4         + view_center_x - map_cols * 2;          /* 0x0001e2c0 */
EdgeX(s) = round((FarX(s) - FullX(s)) / 172 * proj + FullX(s));   /* 0x0001e2f0 */

tile_size - 140 (в раскладке 176x220 - 134), JB_FAR_TILE_SIZE - 4. Округление - не floor и не lround, а точная копия арифметики оригинала:

static int RoundHalfUp(float x)
{
    return (int)((double)x - 0.5) + 1;
}

В s подставляется row_shift[row] + ball_prev_x. Первое слагаемое - изгиб трассы: 50 float, скопированных из g_rowShiftSrc со смещением g_camRow (Level_Generate 0x00013490), то есть уровень «извивается» по заранее заготовленной кривой. Второе - горизонтальная камера: смещается не мяч, а трасса под ним, поэтому используется ball_prev_x, положение мяча на предыдущем кадре.

Вертикальный шаг текстуры

Полосе высотой h пикселей нужно h шагов по 100-пиксельной текстуре. Gfx_BuildTexVStep() (оригинал Game_Init 0x00011560) заполняет 299 строк по 300 элементов: шаг 100.0 / h накапливается и усекается в short, минимум 1. В кадре остаётся только выборка из таблицы.

Камера

Камера - это сам мяч, без сглаживания и без зоны нечувствительности (jb_player.c):

st->cam_row       = (int)st->ball_row;
st->cam_pixel_ofs = (st->ball_row - (float)st->cam_row) * (float)JB_ROW_PIXELS;

JB_ROW_PIXELS = 10. Целая часть выбирает, с какой строки сетки читать тайлы, дробная уезжает в Proj как cam. Финиш уровня - cam_row > 0x3fe (WndProc 0x00020920), при этом сетка размечена только до 0x3fc: последние строки - обнулённый хвост массива, поэтому уровень заканчивается «пустотой», а не обрывом.

Пять тем

Track_DrawFrame вызывает колбэк на каждую строку, а jb_main.c выбирает обработчик по jb_stg.theme (оригинал - таблица переходов в Track_DrawFrame 0x0001e858):

Тема Функция порта Оригинал Спрайты
ICE 0 TrackRow_DrawIce 0x00016e98 лёд, вода (H и V)
DESERT 1 TrackRow_DrawDesert 0x00015b80 песок (H и V), сетка
FOREST 2 TrackRow_DrawForest 0x00017ad4 лес, дерево, дощатый настил
SKY 3 TrackRow_DrawSky 0x000167bc плитка неба, свечение
GRASS 4 TrackRow_DrawGrass 0x00019198 трава, настил, знак
- TrackRow_DrawIceAlt 0x00019e28 вариант льда

Темы 2 и 3 недостижимы. Level_Begin 0x0001376c присваивает theme = menu_index, затем переписывает 2 -> 4 и 3 -> 1, так что лес и небо остаются в коде и в ассетах, но не выпадают ни при каком выборе уровня. Код для них портирован полностью - см. Deviations.

Общие для всех тем детали, вынесенные в jb_trackrow_ctx:

  • JB_KEYED_ROW_FIRST 10 - начиная с этого номера строки блит переключается на вариант с ключом прозрачности; у травы порог свой, 0x64 = 100 (TrackRow_DrawGrass 0x00019370 cmp r0,#0x64);
  • JB_NEAR_ROW_EXTRA 300 - добавка к высоте самой ближней строки;
  • JB_TEX_ANIM_PERIOD 250 / JB_TEX_ANIM_HALF 125 - остаток g_animMs по этим числам переключает кадр воды;
  • JB_GLOW_TINT 250 и JB_GLOW_SPREAD 3.0 - свечение в теме неба (TrackRow_DrawSky 0x00016c9c mov r5,#0xfa).

Фон за трассой едет с коэффициентом JB_PARALLAX 0.08 (Game_DrawFrame 0x0001f37c) и выбирается по теме: 321 лёд, 316 пустыня, 322 лес, 324 небо - см. Levels.

Clone this wiki locally