Stage 3: Binary Space Partition
Порталы 1D: соединяем сектора
Данная страница еще в разработке, уверены, что хотите ознакомиться?
Подключим к BSP-обходу порталы между секторами одинаковой высоты. В таком случае общий сегмент двух секторов больше не должен вести себя как непрозрачная стена: через него нужно видеть геометрию следующего сектора.
Поэтому портальный сегмент мы пропускаем при отрисовке стены и не добавляем его диапазон X в список закрытых участков. Он становится не препятствием, а проходом: BSP продолжает отдавать дальние подсекторы в правильном порядке, и они могут быть нарисованы в оставшемся видимом пространстве.
Здесь важна совместная работа двух идей. BSP задает порядок от ближнего к дальнему, а список закрытых диапазонов показывает, какие части экрана уже окончательно заполнены непрозрачной геометрией. Без BSP дальняя стена могла бы закрыть ближнюю; без диапазонов пришлось бы снова и снова проверять участки экрана, которые уже невозможно изменить.
2.5D Renderer
2D Renderer
Обычные стены по-прежнему закрывают экран от ближних к дальним. Порталы, наоборот, оставляют проем открытым. В результате BSP задает корректный глобальный порядок, а одномерный список диапазонов быстро отбрасывает только те части стен, которые уже скрыты непрозрачной геометрией.
Wall ranges
Wall range — это интервал экранных колонок от xStart до xEnd, который уже полностью закрыт непрозрачной стеной. Поскольку BSP отдает стены от ближних к дальним, содержимое такой колонки больше не изменится: вся следующая геометрия в ней находится за уже нарисованной стеной.
В начале кадра список содержит два служебных диапазона за левой и правой границами экрана. Они ограничивают рабочую область значениями от 0 до ширины экрана и позволяют обрабатывать крайние стены тем же алгоритмом, без отдельных проверок для краев.
Перед рисованием каждой колонки isWallVisible проверяет, не входит ли ее X-координата в закрытый диапазон. После отрисовки addSolidRange добавляет только еще не закрытые части проекции стены. Поэтому диапазоны не перекрывают друг друга, а уже обработанные участки экрана быстро пропускаются.
Такой одномерный клиппинг подходит для сплошной стены: в этом шаге она закрывает колонку целиком потолком, стеной и полом. Портал оставляет внутри колонки видимый проем, поэтому его диапазон нельзя добавлять в wall ranges. Пока соседние секторы имеют одинаковую высоту пола и потолка, этого достаточно: через портал просто виден следующий сектор. Для порталов с разной высотой потребуется отдельное вертикальное отсечение, которое появится на следующем шаге.
Немного кода
Первый фрагмент показывает пропуск портальных сегментов: они не рисуются как стены и не закрывают экран. Следующие фрагменты описывают список закрытых диапазонов и проверку каждой экранной колонки перед рисованием непрозрачной стены.
function render25d(
ctx: CanvasRenderingContext2D,
settings: Settings,
) {
const camera = settings.camera;
const allSegments = settings.level.linedefs;
const bspTree = buildBSPTree(allSegments);
const solidWallRanges = createSolidWallRanges(camera);
traverseBSPTree(bspTree, camera, (bspNode: BSPLeaf) => {
for (const seg of bspNode.segs) {
const sector = seg.frontSector!;
const projection = projectSeg(camera, sector, seg);
if (!projection) {
continue;
}
if (!isPortal(seg)) {
drawSolidWall(ctx, camera, seg, projection, solidWallRanges);
}
}
});
}
interface SolidSegmentRange {
xStart: number;
xEnd: number;
}
function createSolidWallRanges(camera: Camera) {
const ranges: SolidSegmentRange[] = [];
ranges.push({ xStart: Number.MIN_SAFE_INTEGER, xEnd: -1 });
ranges.push({ xStart: camera.screen.width, xEnd: Number.MAX_SAFE_INTEGER });
return ranges;
}
function drawSolidWall(
ctx: CanvasRenderingContext2D,
camera:Camera,
// ..
solidWallRanges: SolidSegmentRange[]
) {
// ..
const xStart = projection.start.screenX;
const xEnd = projection.end.screenX;
const xFrom = Math.max(0, Math.floor(Math.min(xStart, xEnd)));
const xTo = Math.min(camera.screen.width - 1, Math.ceil(Math.max(xStart, xEnd)));
// ..
for (let x = xFrom; x <= xTo; x++) {
if (!isWallVisible(x, solidWallRanges)) {
continue;
}
// рисуем стены
}
addSolidRange(camera, xStart, xEnd, solidWallRanges);
}