Stage 5: Текстурирование
Вертикальное: стены
В прошлых шагах стены были однотонными, теперь будем брать цвет из текстуры, которая хранится как bitmap: двумерный массив индексов и палитра цветов. Сектору достаточно будет знать ID текстуры, и если он не задан, тогда оставляем старое поведение и рисуем стену обычной вертикальной линией.
interface Sector {
// ..
wallTexture: string;
}
2.5D Renderer
2D Renderer
Управление камерой WASD и ZX
Немного кода
Улучшим drawSolidSegment для рассчета координаты текстуры для текущей экранной координаты.
Горизонтальная координата tx показывает, в какой части отрезка стены находится текущий экранный
столбец: ближе к началу стены это значение около 0, ближе к концу — около 1. Из него
получаем texX, то есть столбец внутри текстуры.
Вертикальная координата считается отдельно. Для каждого пикселя между верхом и низом стены берём долю
v: наверху она близка к 0, внизу близка к 1. Так получаем texY.
После этого getTextureColor превращает пару texX/texY в настоящий цвет,
который и попадает на canvas.
function drawSolidSegment(
ctx: CanvasRenderingContext2D,
camera: Camera,
seg: Seg,
angles: IntersectionAngles,
// ..
) {
// ..
for (let x = xFrom; x <= xTo; x++) {
// ..
const tx = getInterpolationFactor(camera, angles, x);
// ..
if (sector.wallTexture) {
const texture = textures[sector.wallTexture];
const texX = Math.floor(tx * texture.width);
for (let y = drawTop; y < drawBottom; y++) {
const v = (y - top) / (bottom - top);
const texY = Math.floor(v * texture.height) % texture.height;
const color = getTextureColor(texture, texX, texY);
drawPixel(ctx, x, y, color);
}
} else {
drawVerticalLine(ctx, x, drawTop, drawBottom, wallColor);
}
// ..
}
// ..
}
Где getInterpolationFactor связывает экранный столбец с положением на реальной стене:
function getInterpolationFactor(
camera: Camera,
angles: IntersectionAngles,
screenX: number,
): number {
const fov = camera.fov.degrees;
const screenWidth = camera.screen.width;
const angle = angles.cameraFrom + (screenX / screenWidth) * fov;
const t = (angle - angles.linedefFrom) / (angles.linedefTo - angles.linedefFrom);
return Math.max(0, Math.min(1, t));
}
Важно, что текстура не рисуется отдельной картинкой поверх стены. Мы по-прежнему строим стену тем же способом: проецируем seg на экран, находим верх и низ вертикальной полосы, а затем меняем только способ заполнения пикселей внутри этой полосы. Благодаря этому текстурирование естественно работает вместе с уже существующими отсечениями, порталами и BSP-порядком.