< /p>
Вы можете ясно видеть, что стена проходит в эллиптической кривой вокруг игрока. На изображении я использую длину вектора направления 1 и плоский вектор длины 2.2 (давая нам большой FOV) < /p>
Я не могу найти никаких неисправностей в алгоритме, и используемая математика должна наиболее вероятно не производить такой результат. Для каждого луча мы должны двигаться в правильном направлении, добавив направление и плоские векторы, масштабированные по x координату камеры. Затем перенести перпендикулярные расстояния в плоскость камеры. < /P>
Есть идеи? 'T изменил алгоритм из исходного учебника. < /p>
Код: Выделить всё
let ZBuffer: { [key: number]: number } = {};
let width = Math.ceil(this.spacing);
this.ctx.save();
for (let column = 0; column < this.resolution; column++) {
// x-coordinate in camera space scaled from -1 to 1
let cameraX = (2 * column) / this.resolution - 1;
// get the ray direction vector
let rayDirX = player.position.dirX + player.position.planeX * cameraX;
let rayDirY = player.position.dirY + player.position.planeY * cameraX;
// which box of the map we're in
let mapX = Math.floor(player.position.x);
let mapY = Math.floor(player.position.y);
// length of ray from current position to next x or y-side
let sideDistX: number;
let sideDistY: number;
//length of ray from one x or y-side to next x or y-side
//these are derived as:
//deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
//deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
//which can be simplified to abs(|rayDir| / rayDirX) and abs(|rayDir| / rayDirY)
//where |rayDir| is the length of the vector (rayDirX, rayDirY). Its length,
//unlike (dirX, dirY) is not 1, however this does not matter, only the
//ratio between deltaDistX and deltaDistY matters, due to the way the DDA
//stepping further below works. So the values can be computed as below.
// Division through zero is prevented
let deltaDistX = Math.abs(1 / rayDirX);
let deltaDistY = Math.abs(1 / rayDirY);
// perpendicular wall distance
let perpWallDist: number;
// what direction to step in x or y-direction (either +1 or -1)
let stepX: number;
let stepY: number;
let hit: number = 0; // was there a wall hit?
let side: number; // was a NS or a EW wall hit? if x then side = 0, if y then side = 1
// calculate step and initial sideDist
if (rayDirX < 0) {
stepX = -1;
sideDistX = (player.position.x - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - player.position.x) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (player.position.y - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - player.position.y) * deltaDistY;
}
// perform DDA
let range = this.range;
while (hit == 0 && range >= 0) {
// jump to next map square, either in x-direction, or in y-direction
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
// Check if ray has hit a wall
if (map.get(mapX, mapY) == 1) hit = 1;
range -= 1;
}
// Calculate distance projected on camera direction. This is the shortest distance from the point where the wall is
// hit to the camera plane. Euclidean to center camera plane would give fisheye effect!
// This can be computed as (mapX - posX + (1 - stepX) / 2) / rayDirX for side == 0, or same formula with Y
// for size == 1, but can be simplified to the code below thanks to how sideDist and deltaDist are computed:
// because they were left scaled to |rayDir|. sideDist is the entire length of the ray above after the multiple
// steps, but we subtract deltaDist once because one step more into the wall was taken above.
if (side == 0) perpWallDist = sideDistX - deltaDistX;
else perpWallDist = sideDistY - deltaDistY;
// SET THE ZBUFFER FOR THE SPRITE CASTING
ZBuffer[column] = perpWallDist; //perpendicular distance is used
// Calculate height of line to draw on screen
let lineHeight: number = this.height / perpWallDist;
// calculate lowest and highest pixel to fill in current stripe
let drawStartY = -lineHeight / 2 + this.height / 2;
let drawEndY = lineHeight / 2 + this.height / 2;
let texture = map.wallTexture;
// calculate value of wallX
let wallX: number; // where exactly the wall was hit
if (side == 0) wallX = player.position.y + perpWallDist * rayDirY;
else wallX = player.position.x + perpWallDist * rayDirX;
wallX -= Math.floor(wallX);
// x coordinate on the texture
let texX = Math.floor(wallX * texture.width);
if (side == 0 && rayDirX > 0) texX = texture.width - texX - 1;
if (side == 1 && rayDirY < 0) texX = texture.width - texX - 1;
this.ctx.globalAlpha = 1;
if (hit) {
let left = Math.floor(column * this.spacing);
let wallHeight = drawEndY - drawStartY;
this.ctx.drawImage(
texture.image,
texX, // sx
0, // sy
1, // sw
texture.height, // sh
left, // dx
drawStartY, // dy - yes we go into minus here, it'll be ignored anyway
width, // dw
wallHeight // dh
);
// this is the shading of the texture - a sort of black overlay
this.ctx.fillStyle = `#000000`;
let alpha =
(perpWallDist +
// step.shading
0) /
this.lightRange -
map.light;
alpha = Math.min(alpha, 0.8);
if (side == 1) {
// give x and y sides different brightness
alpha = alpha * 2;
}
alpha = Math.min(alpha, 0.85);
// ensure walls are always at least a little bit visible - alpha 1 is all black
this.ctx.globalAlpha = alpha;
this.ctx.fillRect(left, drawStartY, width, wallHeight);
this.ctx.globalAlpha = 1;
}
}
Подробнее здесь: https://stackoverflow.com/questions/794 ... fov-angles