Java Game Engine: Raycasted стены полые, сломанные и выглядят как фекалии
Я пытаюсь написать движок лучевого вещания.
Я изучал учебник, найденный на http://www.instructables.com/id/Making-a-Basic-3D-Engine-in-Java/ и учебники по C++ raycasting, найденные на http://lodev.org/cgtutor/raycasting.html, и после нескольких попыток я получил направление лучей в правильном направлении и, следовательно, получил (полу) рабочий результат.
Я заставил стены появляться в мире, и я добавил движение в игру, и я смог передвигаться. Однако стены (которые должны быть кубом) показывают только две стороны куба, независимо от того, в каком направлении я нахожусь в мире. Поэтому вместо того, чтобы показывать сплошной куб, он будет прыгать со стороны куба, которая на самом деле ближе всего к камере, и вместо этого показывать дальнюю сторону куба, и это происходит только тогда, когда я обращен к исходной точке (0,0) 2d массива, в котором хранится моя карта. Эта ошибка показана на изображении выше.
Я думаю, что эта ошибка происходит из-за целочисленного округления и местоположения стенок, обнаруженных лучом, округляемым вниз, но я не могу найти решение. На самом деле я использую два луча для каждого столбца пикселей: один для обнаружения вертикальных стен, а другой - для горизонтальных. Расстояния каждого из них рассчитываются и сравниваются, а затем рисуется стена кратчайшего расстояния.
Мой вопрос: как правильно нарисовать стены
public class Screen {
//VARIABLE DECLARATIONS
//-----------------------
int FOV = 60; //field of view in degrees
int screenwidth = 800; //variable holds the vertical resolution of the screen
int screenheight = 600; //variable holds the horizontal resolution of the screen
double camx; //cameras x coordinate
double camy; //cameras y coordinate
double camAngle; //direction of camera in degrees
double rayAngle; //angle of ray being cast in radians
int x = 0; //holds the current pixel column being looped through
double IncrementAngle = (double)FOV / (double)screenwidth; //calculates the change in the rays angle for each horizontal pixel
int[][] map; //stores the 2d map that represents the 3d world of the game
public Screen() {
public int[] update(int[] pixels, int[][] m, double ca, double cx, double cy, int fov) {
FOV = fov;
IncrementAngle = (double)FOV / (double)screenwidth; //calculates the change in the rays angle for each horizontal pixel
camAngle = ca;
camx = cx;
camy = cy;
map = m;
int x = 0;
Color c; //declares new color
//fills background
for (int n = 0; n < pixels.length; n++) pixels[n] = Color.BLACK.getRGB();
for (double ray = (double)(FOV / 2); ray > (double)(-FOV / 2); ray -= IncrementAngle) {
double vdist = Integer.MAX_VALUE, hdist = Integer.MAX_VALUE;
double perpendicularDist = 0;
double theta;
double lineheight;
int drawstart, drawend;
int side = 0;
int r = 0, g = 0, b = 0, a = 0; //variables that determinbe what colours will be drawn (red, green, blue, and alpha for
transparency)
rayAngle = Math.toRadians(camAngle + ray);
try {
vdist = VertDist(rayAngle);
}
catch (ArrayIndexOutOfBoundsException e) {}
try {
hdist = HorDist(rayAngle);
}
catch (ArrayIndexOutOfBoundsException e) {}
theta = Math.abs(rayAngle - Math.toRadians(camAngle)); //angle difference between the ray being cast and the cameras
direction
if (hdist < vdist) {
perpendicularDist = hdist * Math.cos(theta);
lastSide = 0;
r = Color.GRAY.getRed();
g = Color.GRAY.getGreen();
b = Color.GRAY.getBlue();
a = Color.GRAY.getAlpha();
}
else {
perpendicularDist = vdist * Math.cos(theta);
lastSide = 1;
r = Color.DARK_GRAY.getRed();
g = Color.DARK_GRAY.getGreen();
b = Color.DARK_GRAY.getBlue();
a = Color.DARK_GRAY.getAlpha();
}
//creates pulsating effect with wall colours
r -= pulse;
g += pulse * 2;
b -= pulse;
c = new Color(r, g, b, a);
lineheight = screenheight / perpendicularDist;
drawstart = (int)(-lineheight / 2) + (screenheight / 2);
drawend = (int)(lineheight / 2) + (screenheight / 2);
if (drawstart < 0) drawstart = 0;
if (drawend >= screenheight) drawend = screenheight - 1;
for (int y = drawstart; y < drawend; y++) {
pixels[x + (y * screenwidth)] = c.getRGB();
}
if (x < screenwidth) x++;
else x = 0;
}
//returns pixels array to main class to be shown to screen
return pixels;
}
public double VertDist(double angle) {
double rx = 0, ry = 0;
double stepX = 0, stepY = 0;
double FstepX = 0, FstepY = 0;
double Fxcomp = 0, Fycomp = 0;
double xcomp = 0, ycomp = 0;
double mapx = camx, mapy = camy;
boolean hit = false;
double obliqueDist = 0;
rx = Math.cos(angle);
ry = Math.sin(angle);
if (rx < 0) {
stepX = -1;
FstepX = (camx - ((int)camx)) * stepX;
}
else if (rx > 0) {
stepX = 1;
FstepX = ((int)(camx + 1)) - camx;
}
ycomp = (stepX * Math.tan(angle) * -1);
Fycomp = Math.abs(FstepX) * ycomp;
if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
mapx += FstepX;
mapy += Fycomp;
if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
else {
while (!hit && mapx > 0 && mapy > 0) { //loops while a wall has not been found and while positive indexes are still being
checked
mapx += stepX;
mapy += ycomp;
if (map[(int)(mapx)][(int)(mapy)] > 0) {
hit = true;
//if (Math.toDegrees(rayAngle) < 270 && Math.toDegrees(rayAngle) > 90) {
// mapy -= stepX;
// mapx -= ycomp;
//}
}
}
}
mapx = Math.abs(mapx - camx);
mapy = Math.abs(mapy - camy);
obliqueDist = Math.sqrt((mapx*mapx) + (mapy*mapy));
//change to be not fixed angle based
//if (angle > Math.toRadians(135) && angle < Math.toRadians(225)) {
// obliqueDist -= Math.sqrt(stepX*stepX + ycomp*ycomp);
//}
return obliqueDist;
}
public double HorDist(double angle) {
double rx, ry;
double stepX = 0, stepY = 0;
double FstepX = 0, FstepY = 0;
double Fxcomp, Fycomp;
double xcomp, ycomp;
double mapx = camx, mapy = camy;
boolean hit = false;
double obliqueDist = 0;
rx = Math.cos(angle);
ry = Math.sin(angle);
if (ry < 0) {
stepY = 1;
FstepY = ((int)(camy + 1)) - camy;
}
else if (ry > 0) {
stepY = -1;
FstepY = (camy - (int)camy) * stepY;
}
xcomp = stepY / (Math.tan(angle) * -1);
Fxcomp = Math.abs(FstepY) * xcomp;
if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
mapx += Fxcomp;
mapy += FstepY;
if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
else {
while (!hit) {
mapx += xcomp;
mapy += stepY;
if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
}
}
mapx = Math.abs(mapx - camx);
mapy = Math.abs(mapy - camy);
obliqueDist = Math.sqrt((mapx*mapx) + (mapy*mapy));
//change to be not fixed angle based
//if (angle > Math.toRadians(45) && angle < Math.toRadians(135)) {
// obliqueDist -= Math.sqrt(xcomp*xcomp + stepY*stepY);
//}
return obliqueDist;
} }
1 ответ
Итак, я смог это исправить. Как оказалось, проблема была в целочисленном округлении (координаты стены округлялись бы вниз), как я и думал. Когда лучи были направлены в направлении, где x или y (или оба) приближались к нулю в 2-мерном массиве, координаты стены округлялись бы вниз, расстояние до стены вычислялось бы неправильно, и результат был бы похож на изображение выше.
Я понял, что это происходит, потому что я хранил координаты стены в двойных числах, и хотя двойные числа, безусловно, более точны, чем целые числа, они все еще не ТОЧНЫЕ. Таким образом, происходило то, что координаты стены были бы очень близки к тому, что они должны были быть еще немного смещены, и когда я приводил эти значения к целому числу при проверке столкновений лучей со стенкой, они округлялись до значения под фактическим координировать и предоставить мне неправильное расстояние.
Чтобы исправить это, я добавил очень небольшое значение (около 0,0001), умноженное на направление шага луча (направление шага может быть положительным или отрицательным 1 и определяет перпендикулярное расстояние между последующими вертикальными / горизонтальными линиями сетки) к лучу координаты при проверке столкновений лучей и стен, чтобы уравновесить небольшую неточность моего алгоритма. Короче говоря, это приблизило обнаруженную стену на 0,0001 единицы ближе к игроку, что обошло неточность и привело к тому, что координаты луча были успешно округлены до фактических координат стены.