4

enter image description hereI am attempting to write a raycasting engine.

I studied the tutorial found at http://www.instructables.com/id/Making-a-Basic-3D-Engine-in-Java/ and the C++ raycasting tutorials found at http://lodev.org/cgtutor/raycasting.html, and after a few attempts I got the rays to be cast in the right direction and therefore got a (semi) working output.

I got the walls to show up in the world and i added movement into the game and i was able to move around. However, the walls (which are supposed to be a cube) only show two sides of the cube no matter what direction I'm facing in the world. So instead of showing a solid cube, it'll jump from the side of the cube that is actually closest to the camera, and show the far side of the cube instead, and this only happens when im facing towards the origin (0,0) of the 2d array that my map is stored in. This error is shown in the image above.

I think that this error is due to integer rounding and the locations of the walls detected by the ray being rounded down, yet i cant seem to come up with a solution. I'm actually casting two rays for each pixel column, one to detect vertical walls and one to detect horizontal walls. The distances of each are calculated and compared, and then the shortest distance wall gets drawn.

My question is how to cause the walls to be drawn correctly

 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;
     }     }
xonerex
  • 79
  • 4
  • 2
    Please learn to use paragraphs. A massive wall of text with no logical breaks just puts people off and causes them to skip your question and/or downvote. Then, when writing your question, be concise and to the point. Omit all extraneous information. We don't really need to know the entire history of how you got to this point. Just ask the specific question. – Jim Garrison Jun 14 '16 at 05:29
  • I edited your post for clarity and conciseness. If you teel I removed too much, feel free to revert and edit yourself. However, it would probably be very useful to you to try to reduce your code to a ***minimal*** example that demonstrates the issue. As with your prose, remove anything unrelated to the issue and build an [mcve]. The process of doing so will probably teach you quite a bit and you might even solve your own problem. It always works that way for me. – Jim Garrison Jun 14 '16 at 05:38
  • Thank you for actually leaving some constructive criticism on my post instead of straight up downvoting and leaving. Ill keep what you said in mind next time i post. I was actually just about to remove all the unnecessary bits myself so thank you for that. – xonerex Jun 14 '16 at 05:41
  • You clearly have some talent and could learn to be a good developer, but as you acknowledge, you are young and still a beginner. So I don't want to discourage you. Communicating clearly and succinctly is an extremely important skill, as is the ability to examine a problem and extract the important parts from the noise. Good luck! – Jim Garrison Jun 14 '16 at 05:44
  • Thank you for the help! It also doesnt help that im incredibly sleep deprived at the moment. – xonerex Jun 14 '16 at 05:51
  • 1
    you need to start debugging. See my Wolfenstein example images in this QA http://stackoverflow.com/a/32733650/2521214 especially the top left corner 2D map preview with the rays included. Try to add the same to your App so you can directly see what is wrong with your rays. If they are correct then check the V lines rendering if they got computed the position and size correctly. Lastly check anti fish-eye. ... you should also post some images so someone could may be spot anything ,,, – Spektre Jun 14 '16 at 05:54
  • @xonerex you could start with rendering a dot for each ray wall collision (do not stop on closest wall) and have separate colors for H/V wall side collision. This way you could see if there is not some pattern in the problem revealing it self. – Spektre Jun 14 '16 at 06:08
  • As weird as it sounds to ask for *more* code, could you link to the entire project? I understand that this kind of thing is hard to minimize, so being able to interact with the code would actually help. – Zarkonnen Jun 14 '16 at 13:08
  • Thanks for the suggestions Spektre. Ill definitely try that out. And Zarkonnen, here's a dropbox link to the entire project: https://www.dropbox.com/sh/eyfy34v8h2ke7a0/AADv3Pb7NTsv1DHV_sLSGttQa?dl=0 (everything's disassembled, so if you want to run it you'll have to create some folders and move some files). – xonerex Jun 14 '16 at 14:16
  • Blind, semi-educated guess: Try changing the order (orientation) of the faces of your cube. So if one triangle now consists of vertices (v0, v1, v2), change it to (v0, v2, v1) and see whether it works. If it does not work **then**, you can start "serious" debugging. – Marco13 Jun 14 '16 at 14:32
  • @xonerex the image looks like you are selecting the farer intersection instead of the closer one to viewer. – Spektre Jun 14 '16 at 20:28

1 Answers1

2

Okay so I was able to fix it. As it turns out, the issue was due to integer rounding (wall coordinates would get rounded down) just as I had thought. When the rays were cast in a direction where x or y (or both) were approaching zero in the 2d array, the wall coordinates would get rounded down, the distance to the wall would be calculated incorrectly, and the result would look like the picture above.

I figured out this was happening because I was storing the wall coordinates in doubles, and although doubles are certainly more accurate than integers, they still aren't EXACT. So what was happening was that the wall coordinates would be very close to what they should have been yet slightly off, and when i casted these values to an integer while checking for ray-wall collisions, they would round down to the value under the actual coordinate and provide me with an incorrect distance.

So to fix this, I added a very small value (around 0.0001) multiplied by the step direction of the ray (step direction can either be positive or negative 1 and determines the perpendicular distance between subsequent vertical/horizontal array grid lines) to the ray coordinates while checking for ray-wall collisions in order to balance out the slight inaccuracy of my algorithm. In short, what this did was bring the detected wall 0.0001 units closer to the player, therefore bypassing the inaccuracy and causing the ray coordinates to be successfully rounded down to the actual coordinates of the wall.

xonerex
  • 79
  • 4
  • 1
    never ever compare floating point numbers for equality. always compare difference to delta. – MK. Jun 17 '16 at 03:24
  • Yeah I understand that now. Floating point values just cant be trusted – xonerex Jun 23 '16 at 20:44
  • You have a lot to learn. It's not just floating numbers. No numbers can be trusted. Or people for that matter. – MK. Jun 24 '16 at 00:01