mirror of
https://github.com/Tolik-Trek/DOOM2.git
synced 2026-06-15 00:51:33 +03:00
179 lines
7.7 KiB
Lua
179 lines
7.7 KiB
Lua
-- =====================================================================
|
||
-- Генератор d2_table.tbl (рейкастер DOOM2 / Sprinter Sp2000)
|
||
-- 16-шаговая ближняя зона, формат-совместимый с Bin/d2_table.tbl.
|
||
--
|
||
-- Запуск:
|
||
-- sjasmplus --nologo Resources/tools/gen_d2_table/gen_d2_table.asm
|
||
-- (из КОРНЯ проекта; эмулятор не нужен)
|
||
--
|
||
-- Вход: нет.
|
||
-- Выход: Build/d2_table.gen.tbl
|
||
-- (Bin/d2_table.tbl — канонический рабочий ассет, не трогаем).
|
||
--
|
||
-- Документация формата и математики: INFO/doom-энжин.md.
|
||
--
|
||
-- ФОРМАТ (как в оригинале):
|
||
-- * 524288 байт = 32 страницы по 16384 (PAGE).
|
||
-- * Страницы парами CORNER=0..15: чётная = «offset», нечётная = «projection».
|
||
-- * Каждая страница = 512 записей по 32 байта (REC).
|
||
-- * Запись (32 б):
|
||
-- EVEN: byte[0..15] = низкий байт адреса DDA-клетки в отн.карте
|
||
-- TABLE_W (квадрант 16×16, страйд 1, base=#2000+QUAD*256);
|
||
-- byte[16..31] = 0.
|
||
-- ODD : byte[0..15] = код высоты (индекс в table_x.tbl);
|
||
-- byte[16..31] = текстурная u-координата столбца стены.
|
||
-- * Адресация записи в кадре: H ∈ #40..#7F (6 бит = крупный угол
|
||
-- внутри квадранта), L_top3 ∈ {0,32,…,224} (3 бита = подгруппа
|
||
-- колонок); вместе 64×8 = 512 = страница. За кадр движок проходит
|
||
-- 40 H × 8 L = 320 экранных лучей (60°/90° квадранта).
|
||
--
|
||
-- МАТЕМАТИКА (см. INFO/doom-энжин.md §«Закон проекции»):
|
||
-- * supercover-DDA, tie tx<=ty -> X (как в оригинале).
|
||
-- * перспектива по перпендикулярной дистанции: N = K / perp, K=315;
|
||
-- perp = d_eucl * cos(ray_ang - view_ang) (fisheye-фикс).
|
||
-- * height_code = index в table_x.tbl, грубо A = clamp((256-N)/2, 0..127).
|
||
-- * u_col = round(u_fraction * 64) & 63.
|
||
--
|
||
-- CORNER (0..15) кодирует субпозицию игрока в клетке (4×4).
|
||
-- Запись (record_idx 0..511) кодирует «виртуальный экранный угол»
|
||
-- внутри квадранта: ray_ang = (record_idx + 0.5)/512 * (pi/2).
|
||
-- =====================================================================
|
||
|
||
local PAGES = 32
|
||
local PAGE = 16384
|
||
local REC = 32
|
||
local RECS = PAGE // REC -- 512
|
||
local STEPS = 16
|
||
local SUBPOS = 16
|
||
local GRID = 16 -- TABLE_W квадрант = 16×16
|
||
local K_PROJ = 315.0
|
||
local FOV_R = math.pi / 3.0 -- 60°
|
||
local SCR_W = 320
|
||
local QUAD = math.pi / 2.0 -- 90°
|
||
|
||
local OUT = "Build/d2_table.gen.tbl"
|
||
|
||
-- Субпозиция CORNER=0..15 -> (sx, sy) в [0..1] (углы 1/8 от стенки).
|
||
local function corner_xy(c)
|
||
local sx = (c % 4) / 4.0 + 1.0 / 8.0
|
||
local sy = (c // 4) / 4.0 + 1.0 / 8.0
|
||
return sx, sy
|
||
end
|
||
|
||
-- supercover-DDA, 16 шагов. Возвращает массив { {cx,cy,d_eucl,u}, ... }.
|
||
-- Ось +X = «вперёд» (вид игрока). cx,cy — клеточные координаты относит.
|
||
-- стартовой клетки (в которой игрок).
|
||
local function trace(sx, sy, ang)
|
||
local dx = math.cos(ang); if math.abs(dx) < 1e-9 then dx = (dx < 0 and -1e-9 or 1e-9) end
|
||
local dy = math.sin(ang); if math.abs(dy) < 1e-9 then dy = (dy < 0 and -1e-9 or 1e-9) end
|
||
local x, y = sx, sy
|
||
local cx, cy = math.floor(x), math.floor(y)
|
||
local out = {}
|
||
for _ = 1, STEPS do
|
||
local nx = cx + (dx > 0 and 1 or 0)
|
||
local ny = cy + (dy > 0 and 1 or 0)
|
||
local tx = (nx - x) / dx
|
||
local ty = (ny - y) / dy
|
||
local u
|
||
if tx <= ty then
|
||
x = nx; y = y + dy * tx; cx = cx + (dx > 0 and 1 or -1)
|
||
u = y - math.floor(y)
|
||
else
|
||
y = ny; x = x + dx * ty; cy = cy + (dy > 0 and 1 or -1)
|
||
u = x - math.floor(x)
|
||
end
|
||
local de = math.sqrt((x - sx)^2 + (y - sy)^2)
|
||
out[#out + 1] = { cx, cy, de, u }
|
||
end
|
||
return out
|
||
end
|
||
|
||
-- offset байта = (cy & (GRID-1)) * GRID + (cx & (GRID-1))
|
||
-- (вне квадранта 16×16 «оборачиваем» по 8-битной арифметике
|
||
-- движка; реальный движок попадает в нулевые ячейки relmap).
|
||
local function off_byte(cx, cy)
|
||
local oc = cx % GRID
|
||
local oy = cy % GRID
|
||
if oc < 0 then oc = oc + GRID end
|
||
if oy < 0 then oy = oy + GRID end
|
||
return (oy * GRID + oc) & 0xFF
|
||
end
|
||
|
||
local function height_code(N)
|
||
if N <= 256 then
|
||
local A = (256 - N) // 2
|
||
if A < 0 then A = 0 end
|
||
if A > 127 then A = 127 end
|
||
return A
|
||
end
|
||
-- full-screen с клипом: A = 128 + 6-битный буфер-offset (как в оригинале).
|
||
local over = (N - 256) // 8
|
||
if over > 63 then over = 63 end
|
||
return 128 + over
|
||
end
|
||
|
||
local function ucol(uf)
|
||
local v = math.floor(uf * 64.0 + 0.5)
|
||
return v & 63
|
||
end
|
||
|
||
-- Кадр движка читает 320 экранных лучей подряд из 512 записей. Поэтому
|
||
-- виртуальный угол луча в кадре = view_ang + atan2(cam_x, halfproj),
|
||
-- где cam_x шагает по 320. Но в таблице 512 записей покрывают 90° квадранта,
|
||
-- так что одной записи соответствует ray_ang = ((idx + 0.5)/512) * 90°
|
||
-- (без поправки fisheye-камеры — она применяется через perp ниже).
|
||
local function ray_angle(record_idx)
|
||
return (record_idx + 0.5) / RECS * QUAD
|
||
end
|
||
|
||
local function gen_pair(corner)
|
||
local sx, sy = corner_xy(corner)
|
||
local even = {}; for i = 1, PAGE do even[i] = 0 end
|
||
local odd = {}; for i = 1, PAGE do odd[i] = 0 end
|
||
-- view_ang = 0 (ось +X — вперёд); MAKE_MAP в движке поворачивает уровень
|
||
-- под квадрант. Луч ray_ang отсчитывается от +X.
|
||
for r = 0, RECS - 1 do
|
||
local ang = ray_angle(r)
|
||
local path = trace(sx, sy, ang)
|
||
local base = r * REC
|
||
for k = 1, STEPS do
|
||
local cx, cy, de, u = path[k][1], path[k][2], path[k][3], path[k][4]
|
||
-- EVEN: offset байт в relmap (TABLE_W) для шага k.
|
||
even[base + k] = off_byte(cx, cy)
|
||
-- ODD : код высоты + текстурная u.
|
||
local perp = de * math.cos(ang - 0.0) -- view_ang=0 -> perp=de*cos(ang)
|
||
if perp < 0.05 then perp = 0.05 end
|
||
local N = K_PROJ / perp
|
||
odd[base + k] = height_code(N) & 0xFF
|
||
odd[base + STEPS + k] = ucol(u)
|
||
end
|
||
end
|
||
return even, odd
|
||
end
|
||
|
||
local out = {}
|
||
for i = 1, PAGES * PAGE do out[i] = 0 end
|
||
|
||
for c = 0, SUBPOS - 1 do
|
||
local even, odd = gen_pair(c)
|
||
local eb = (c * 2) * PAGE
|
||
local ob = (c * 2 + 1) * PAGE
|
||
for i = 1, PAGE do
|
||
out[eb + i] = even[i]
|
||
out[ob + i] = odd[i]
|
||
end
|
||
end
|
||
|
||
do
|
||
local t = {}
|
||
for i = 1, #out do t[i] = string.char(out[i] & 0xFF) end
|
||
local w = io.open(OUT, "wb"); w:write(table.concat(t)); w:close()
|
||
end
|
||
|
||
print(string.format("[gen_d2_table] %s размер=%d б = %d страниц × %d б",
|
||
OUT, PAGES * PAGE, PAGES, PAGE))
|
||
print(string.format("[gen_d2_table] CORNER=0..15, 16 шагов, %d записей × %d б на страницу",
|
||
RECS, REC))
|
||
print("[gen_d2_table] NB: формат-совместим, но не бит-в-бит с Bin/d2_table.tbl")
|
||
print("[gen_d2_table] (оригинальный fixed-point утерян; см. INFO/doom-энжин.md).")
|