0

So I've been writing a delphi conversion of this untextured raycaster. However, when checking for errors, I noticed that if you walk directly forwards a zero division error occurs. This makes sense as, when it does occur, the camera is inside of a wall so the perpendicular distance between the camera and that wall is zero. However, what doesn't make sense to me is that the check for being able to move forwards should stop the player from being able to enter a wall. What adds to this confusion is that the error only happens when walking forwards and not backwards (even though the test done for each is essentially the same). From debugging, I have been able to find that the error only occurs when the camera is at the starting rotation. I have attempted to fix the issue by setting the height of the line drawn to 480 (the screen size), and while that stops the program from crashing, you get this error, in which you can walk through a wall.
tldr, the raycaster is throwing a zero division error and I'm not sure how to fix it, please help.
Source code
edit: here is the required part of the source code:

procedure TForm1.ScreenRender();
var
 Count : Integer;

CameraX : Double;
rayPosX : Double;
rayPosY : Double;
rayDirX : Double;
rayDirY : Double;

mapX        : Integer;
mapY        : Integer;
sideDistX   : Double;
sideDistY   : Double;
deltaDistX  : Double;
deltaDistY  : Double;
perpWallDist: Double;
stepX       : Integer;
stepY       : Integer;
hit         : Integer;
side        : Integer;

lineHeight  : Integer;
drawStart   : Integer;
drawEnd     : Integer;
begin
  for Count := 0 to 639 do
  begin

CameraX := 2 * Count/639-1;
rayPosX := posX;
rayPosY := posY;                               
rayDirX := dirX + planeX * CameraX;
rayDirY := dirY + planeY * CameraX;

mapX := Trunc(rayPosX);
mapY := Trunc(rayPosY);
deltaDistX := sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
deltaDistY := sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
hit := 0;

if(rayDirX < 0) then
begin
  stepX:=-1;
  sideDistX := (rayPosX - mapX) * deltaDistX;
end

else                                                                        
begin                                                              
  stepX := 1;
  sideDistX := (mapX + 1 - rayPosX) * deltaDistX;
end;

if(rayDirY < 0) then
begin
  stepY:=-1;
  sideDistY := (rayPosY - mapY) * deltaDistY ;
end

else
begin
  stepY:=1;
  sideDistY := (mapY + 1 - rayPosY) * deltaDistY;
end;

while(hit = 0) do
begin
  if(sideDistX < sideDistY) then
  begin
    sideDistX := sideDistX + deltaDistX;
    mapX := mapX + stepX;
    side := 0;
  end

  else
  begin
    sideDistY := sideDistY + deltaDistY;
    mapY := mapY + stepY;
    side:= 1;
  end;

  if(MapData[mapX,mapY] > 0) then
    hit := 1;
end;


if(side = 0) then
  perpWallDist := (mapX - rayPosX + (1 - stepX) / 2) / rayDirX
else
  perpWallDist := (mapY - rayPosY + (1 - stepY) / 2) / rayDirY;


    //the error is here
    //as the camera is in the wall and perpWallDist is 0, a zero division error is thrown
  //if(perpWallDist > 0) then
    lineHeight := Trunc(480 /perpWallDist);
  //else
  //  lineHeight := 480;
drawStart := round(-lineHeight / 2 + 480 / 2);
if(drawStart < 0) then
  drawStart := 0;                                                           
drawEnd := round(lineHeight / 2 + 480 / 2);
if(drawEnd >= 480) then
  drawEnd := 480;                                                                 

case MapData[mapX,mapY] of                                                                    
  1: image1.Canvas.Pen.Color := $ff0000;
  2: image1.Canvas.Pen.Color := $0000ff;
  3: image1.Canvas.Pen.Color := $00ff00;
  4: image1.Canvas.Pen.Color := $ffffff;
end;

if(side = 1) then
  image1.Canvas.Pen.Color := round( image1.Canvas.Pen.Color / 1.5);

image1.Canvas.MoveTo(Count,drawStart);                                         
image1.Canvas.LineTo(Count,drawEnd);

end;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: 
TShiftState);
const
moveSpeed = 0.5;
rotSpeed = 0.25;
var                                                                               
oldDirX    : Double;
oldPlaneX  : Double;
begin                                                                                        
if(Key = $57) and (MovAllow = true) then
begin
if(MapData[trunc(posX + dirX * moveSpeed),trunc(posY)] = 0) then
  posX := posX + (dirX * moveSpeed);
if(MapData[trunc(posX),trunc(posY + dirY * moveSpeed)] = 0) then
  posY := posY + (dirY * moveSpeed);
end;                                                                                    

if(Key = $53) and (MovAllow = true) then
begin
if(MapData[trunc(posX - dirX * moveSpeed),trunc(posY)] = 0) then
  posX := posX - (dirX * moveSpeed);
if(MapData[trunc(posX),trunc(posY - dirY * moveSpeed)] = 0) then
  posY := posY - (dirY * moveSpeed);                                                    
end;


if(Key = $44) and (MovAllow = true) then
begin
oldDirX := dirX;
dirX := dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY := oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
oldPlaneX := planeX;
planeX := planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY := oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
end;

if(Key = $41) and (MovAllow = true) then
begin
oldDirX := dirX;
dirX := dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY := oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
oldPlaneX := planeX;
planeX := planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY := oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
end;


end;

edit 2: dirX and dirY only change when pressing 'A' or 'D' (key = $41 is A and key = $44 is D). They are changed using the rotation matrix. The code for them changing is just above this edit at the base of the code. From debugging, I've found that if you walk in a straight line they are not changed (this is expected). I also found that if you rotate the camera slightly in one direction and then rotate it back to what seems like the starting rotation, even though the dirX and dirY values are slightly different (instead of the initial values, they have dirX = -1 and dirY = 1.98408997564847E-0017) it still causes the same crash. I assume this is as they are almost the same values.

DoYouEvenFish
  • 35
  • 1
  • 6
  • [mcve] please.... – David Heffernan Dec 23 '17 at 18:09
  • I've updated the source code, so it should be easier to spot the error. The code that is commented out around the line throwing the error can be uncommented to reproduce the error seen above. If these lines are left commented out, a zero division error is thrown when entering the wall. – DoYouEvenFish Dec 23 '17 at 18:22
  • 1
    There is no code here. We need a [mcve]. Alternatively you could debug your program. – David Heffernan Dec 23 '17 at 18:46
  • 3
    You shouldn't expect readers to follow external links to see your source code - it could be mined with malware. – MartynA Dec 23 '17 at 18:47
  • I've edited the post so that the required parts of the source code are in the post. There is a comment around the line that causes the zero division error, and as I said earlier the lines of code around it can be uncommented to reproduce the error. – DoYouEvenFish Dec 23 '17 at 19:26
  • Why is the divisor zero? Did you do any debugging at all? Do you know how to debug? – David Heffernan Dec 23 '17 at 20:57
  • Yes I did try to debug the program. The divisor is only zero in the case where the player is inside of a wall. This is why the program only crashes when you walk into a wall. As I stated earlier, from the checks done before moving the player, you shouldn't be able to walk into a wall and you can only walk into a wall at the initial rotation. – DoYouEvenFish Dec 23 '17 at 21:11
  • Well, clearly the divisor is zero. So you need to avoid that. – David Heffernan Dec 23 '17 at 22:29
  • I know that I need avoid having zero as the divisor. This needs to be avoided by stopping the player enter a wall. However, as stated earlier, I don't understand why the program allows the player to enter a wall when at the initial rotation. – DoYouEvenFish Dec 23 '17 at 22:52
  • Do some debugging to find out why that happens. We aren't a debugging service. – David Heffernan Dec 23 '17 at 23:05
  • I have been trying to debug the program, however after spending about a week trying to fix it I have made next to no progress. That is why I asked about it here. – DoYouEvenFish Dec 23 '17 at 23:23
  • I can't see evidence of debugging. I'd expect to see trace logging so that you can find out where your expectations of behaviour deviate from its actual behaviour. – David Heffernan Dec 24 '17 at 16:22
  • I'm not sure what you mean by trace logging, but if you mean checking the variable at specific points, I have. I removed it from the code I gave earlier but I made the program output the required variables to a file on each run of the loop so that I could try to figure out what had gone wrong. I couldn't use a watch and breakpoints as the loop has to run 640 times every 1/60 th of a second (Which it does). After attempting to debug the program, I came to the conclusion that it was crashing because the perpendicular distance was 0 as the player was inside of a wall. – DoYouEvenFish Dec 24 '17 at 16:36
  • However, after checking the procedure for movement, I don't understand why it lets the player enter a wall and why only on the initial rotation. – DoYouEvenFish Dec 24 '17 at 16:37
  • You just need to keep debugging. That's your job not ours. Good luck. – David Heffernan Dec 24 '17 at 16:53
  • I guess I'll keep doing that then. Also (kind of unrelated), as I am new to Delphi if you could point me towards any useful learning resources about it, that would be very useful. – DoYouEvenFish Dec 24 '17 at 20:34

1 Answers1

1

Just run the program in the debugger, and Delphi will tell you on which line the error occurs.

Even without that, you can check if all places where you divide are safe.

Look at this line:

deltaDistX := sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));

What happens when rayDir is 0?

perpWallDist := (mapX - rayPosX + (1 - stepX) / 2) / rayDirX

What happens when rayDir is 0?

lineHeight := Trunc(480 /perpWallDist);

What happens when perpWallDist is 0?

Update Why could perpWallDist be 0? Well, place a breakpoint at the place where it's being set, and look at the values that are used to set the variable.

perpWallDist := (mapX - rayPosX + (1 - stepX) / 2) / rayDirX

For the rest I cannot help you further. This is just a matter of plain debugging. If you get stuck with that process somewhere, create a separate question about what you run into.

Wouter van Nifterick
  • 23,603
  • 7
  • 78
  • 122
  • The OP seems to understand *what happens* when a divisor is zero. The q seem to be about *why* certain divisors get to be zero in the first place. – MartynA Dec 24 '17 at 18:09
  • Yeah, the question was about why the divisor becomes zero. – DoYouEvenFish Dec 24 '17 at 20:35
  • Due to the number of times the program has to loop, breakpoints are inconvenient. However, I was able to get the values of the variables by having them written to a file. From that I have found that when the program crashes, rayposX = -mapX -1 and StepX = -1. If we plug these values into the calculation above, we can see that it comes out as mapX - mapX / rayDirX. This returns as zero as mapX - mapX = 0. This is where the error is coming from. I haven't yet found a fix to this issue, however I will add it here once I do so that both I and others can learn from where I went wrong. – DoYouEvenFish Dec 24 '17 at 22:33
  • Just realized that I made a mistake with what I said there. rayPosX is actually equal to mapX -1, not -mapX -1. StepX is still equal to -1 though and the second part is still correct in that it comes out as mapX - mapX / rayDirX. – DoYouEvenFish Dec 24 '17 at 22:48
  • 1
    @DoYouEvenFish In that case a tip: right-click on the breakpoint dot in the gutter area, and choose breakpoint properties. You can enter a boolean expression to make the debugger stop. For example `i=150`. That way, the debugger stops when `i` reaches 150. – Wouter van Nifterick Dec 24 '17 at 22:51
  • Thank you, that will make debugging a lot easier and it should also make it faster. – DoYouEvenFish Dec 24 '17 at 23:03
  • 1
    @DoYouEvenFish Another debugging tip: You can also set what's called a `Data break-point` bound to a specific variable (effectively bound to a memory location). Then the program will break whenever that memory changes from anywhere in code. – Disillusioned Dec 27 '17 at 01:03