[C++] Raycasting - Drawing the Rear of a Cube? [SOLVED]

Started by Scavenger, Tue 29/12/2015 14:06:12

Previous topic - Next topic

Scavenger

I'm trying to write a raycaster for AGS (that can be controlled from within AGS using sprites as maps and stuff), and it's going pretty well. I currently have a very very simple raycaster working, based on this tutorial, and I've added light levels and stuff to it, and am currently working on transparent walls. Unfortunately, I've run into a problem I can't solve myself, and I'm not sure where else to turn to. I know I'm asking a lot of non-AGSScript questions, but I really can't do this in AGSScript without it requiring a futuristic supercomputer.

My problem is that I can't seem to get the back face of a cube to render properly. This is what I got so far:


The cube has the pillar texture on all four faces, so in reality, it's got to look something like this, with the pillar texture visible at the back as well. But my attempts, well, they're not going well. I end up with an infinite line of "other walls", instead of just the one I wanted. You can see that the opposite wall of the cube just goes on forever when you look into it. I'm not even sure how it's doing that. And the adjacent wall isn't drawn at all, to be expected, but I'm not sure how to do that either.



My method is:
1) Cast ray
2) Continue until it hits a wall.
2) Start drawing that wall, using the ZBuffer to mask out anything already drawn in front of it.
3) If the wall has any transparent pixels, flag the thing to draw deeper.
4) On the next pass, draw the back face by flipping the ray direction and manually stepping to the next intersection.
5) Goto 2 afterwards to draw the wall behind it.

I'm sure I'm doing this wrong, but I'm not sure how to fix it, I don't understand the code well enough to do anything constructive with it. Is there anything I can do to stop this? Is there a better way to be doing this?

My code for drawing walls is:
Code: C++
 while (hit == 0 && deeper == true)
{
	if (opposite)
	{
		rayDirX = rayDirX * (-1);
		rayDirY = rayDirY * (-1);
		stepX = stepX * (-1);
		stepY = stepY * (-1);
        }
	else if (sideDistX < sideDistY) //jump to next map square, OR in x-direction, OR in y-direction
        {
          sideDistX += deltaDistX;
          mapX += stepX;
          side = 0;
        }
        else
        {
          sideDistY += deltaDistY;
          mapY += stepY;
          side = 1;
        }
        //Check if ray has hit a wall       
        if (worldMap[mapX][mapY] > 0)
		{
			hit = 1; //Set this to true so that by default, it's impossible to hang the engine.
			deeper = false; //Set this to false so that we don't go deeper than we need to.

			//Calculate distance of perpendicular ray (oblique distance will give fisheye effect!)    
			if (side == 0) perpWallDist = fabs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX);
			else       perpWallDist = fabs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY);
      
			 //Calculate height of line to draw on screen       
     		 int lineHeight = abs(int(h / perpWallDist));
     		 
     		 //calculate lowest and highest pixel to fill in current stripe
     		 drawStart = -lineHeight / 2 + h /2;
     		 if(drawStart < 0) drawStart = 0;
     		 drawEnd = lineHeight / 2 + h / 2;
     		 if(drawEnd >= h) drawEnd = h - 1;
     		 //texturing calculations
     		 int texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
     		 
     		 //calculate value of wallX
     		 if (side == 1) wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX;
     		 else       wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY;
     		 wallX -= floor((wallX));
     		  
     		 //x coordinate on the texture
	 		 int wall_light=0;
     		 int texX = int(wallX * double(texWidth));
     		 if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
     		 if(side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
	 	 if (rayDirX > 0 && side == 0) wall_light = 255 / (8-lightMap [(int)mapX-1][(int)mapY]);
	 	 if (rayDirX < 0 && side == 0) wall_light = 255 / (8-lightMap [(int)mapX+1][(int)mapY]);
	 	 if (rayDirY > 0 && side == 1) wall_light = 255 / (8-lightMap [(int)mapX][(int)mapY-1]);
	 	 if (rayDirY < 0 && side == 1) wall_light = 255 / (8-lightMap [(int)mapX][(int)mapY+1]);
     		 for(int y = drawStart; y < drawEnd; y++)
     			{
				if (ZBuffer[x][y] > perpWallDist || ZBuffer[x][y] == 0) //We can draw.
				{
     				    int d = y * 256 - h * 128 + lineHeight * 128; //256 and 128 factors to avoid floats
     			 	    int texY = ((d * texHeight) / lineHeight) / 256;
     				    int color = texture[texNum][texWidth * texY + texX];
     				    if (color > 0)
					{
					    color = MixColorAlpha (color,GetColor565(0,0,10),wall_light);
					    buffer[y][x] = color;
					    //SET THE ZBUFFER FOR THE SPRITE CASTING
					    ZBuffer[x][y] = perpWallDist; //perpendicular distance is used
					}
					else
					{
					    //We found transparency, we have to draw deeper.
					    deeper = true;
					    hit = 0;
					}
				}
     			}
			 if (opposite)
			 {
				 opposite = false;
				 rayDirX = rayDirX * (-1);
				 rayDirY = rayDirY * (-1);
				 stepX = stepX * (-1);
				 stepY = stepY * (-1);
			 }
			 else if (!opposite && deeper && (prevmapX != mapX && prevmapY != mapY))
			 {
				 opposite = true;
				 prevmapX = mapX;
				 prevmapY = mapY;
			 }
			//End of wall drawing functions.
			}
		//End of loop.
}



Full code is here in case I've missed anything out.

Thank you in advance, and sorry for filling the technical forum.

Mandle

Ye gods man!!!

DOOM type game in AGS?!

AWESOME!!!!

Khris

I'd check for the texture being partly transparent (using a flag, or texture IDs in a certain range), and if so, keep the ray going until it hits a standard wall, storing walls (texture ID, texture coord, size) in between in a list. After rendering the far wall, go back and render the other pixel columns in reverse order.

I'll also offer an alternative solution that would require a slightly different engine, but this method of showing a column is kinda weird anyway, isn't it?

My solution is to use lines in a 2D space for the walls (those can relatively easily be generated from an ascii map). Objects like the column are then implemented just like walls, only that they will constantly rotate so the "wall" always runs perpendicular to the player's line of sight.
That way the column appears as scaled 2D sprite but the existing 2.5D engine can be used to render it, proper clipping and all (afaik that's how Wolfenstein showed objects and enemies). One could even implement proper lighting by setting a cylinder flag for the object and using the texture coordinate to alter the lighting calculation.

Scavenger

#3
Khris : I've already implemented objects like that, but this is for fences, doors, and other wall types that the  player needs to see through. I'm just using the column texture because it's already on the texture map.

My biggest problem thus far is trying to get the ray caster to hit the back walls of a cube at all, I'm not sure how to do it. The method I tried just renders it to the front wall texture and that's definitely not what I need. How do I get the ray to hit the rear face at all?

EDIT: It's been solved, I needed to flip the signs on a lot of the math in case of drawing the opposite wall, and offset the floorcasting by one unit to compensate:



This'll be useful for all of my fence, door, and mysterious shaft of light needs!

Though now I have a new problem: Drawing the back face without first drawing the front face, like so:



The wall only gets drawn if the front face is drawn first (which triggers the Opposite flag), but I'm not sure how to make it so that the opposite wall is drawn when it's visible, rather than when the ray passes right through the block. You can see that the left block there makes the opposite side of the right block visible, but it doesn't draw the back face. What IS good is that fully solid blocks are drawn as well as the transparent blocks, with no z-fighting.

Code: C++

void Raycast_Render (int slot)
{
	long w=0,h=0;
	BITMAP *screen = engine->GetSpriteGraphic (slot);
	engine->GetBitmapDimensions (screen,&w,&h,NULL);
	unsigned char** buffer = engine->GetRawBitmapSurface (screen);
	for (int x = 0;x<w;x++)
	{
		for (int y=0;y<h;y++)
		{
			ZBuffer[x][y] = 0;
		}
	}
  //start the main loop
    for(int x = 0; x < w; x++)
    {
      //calculate ray position and direction 
      double cameraX = 2 * x / double(w) - 1; //x-coordinate in camera space     
      double rayPosX = posX;
      double rayPosY = posY;
      double rayDirX = dirX + planeX * cameraX;
      double rayDirY = dirY + planeY * cameraX;
     
      //which box of the map we're in  
      int mapX = int(rayPosX);
      int mapY = int(rayPosY);
       
      //length of ray from current position to next x or y-side
      double sideDistX;
      double sideDistY;
       
      //length of ray from one x or y-side to next x or y-side
      double deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
      double deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
      double perpWallDist;
       
      //what direction to step in x or y-direction (either +1 or -1)
      int stepX;
      int stepY;
      int prevmapX=0;
	  int prevmapY=0;
      int hit = 0; //was there a wall hit?
      int side; //was a NS or a EW wall hit?
	  
      //calculate step and initial sideDist
      if (rayDirX < 0)
      {
        stepX = -1;
        sideDistX = (rayPosX - mapX) * deltaDistX;
      }
      else
      {
        stepX = 1;
        sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX;
      }
      if (rayDirY < 0)
      {
        stepY = -1;
        sideDistY = (rayPosY - mapY) * deltaDistY;
      }
      else
      {
        stepY = 1;
        sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY;
      }
      //perform DDA
	  bool deeper = true;
	  bool opposite = false;
	  bool oppositedrawn = false;
	  double wallX; //where exactly the wall was hit
	  int drawStart;
	  int drawEnd;
      while (hit == 0 && deeper == true)
	  {
		if (opposite)
		{
			  rayDirX = rayDirX * (-1);
			  rayDirY = rayDirY * (-1);
			  stepX = stepX * (-1);
			  stepY = stepY * (-1);
			  if (sideDistX < sideDistY) side = 0;
			  else side = 1;
		}
		else if (sideDistX < sideDistY) //jump to next map square, OR in x-direction, OR in y-direction
        {
          sideDistX += deltaDistX;
          mapX += stepX;
          side = 0;
        }
        else
        {
          sideDistY += deltaDistY;
          mapY += stepY;
          side = 1;
        }
        //Check if ray has hit a wall       
        if (worldMap[mapX][mapY] > 0)
		{
			hit = 1; //Set this to true so that by default, it's impossible to hang the engine.
			deeper = false; //Set this to false so that we don't go deeper than we need to.

			//Calculate distance of perpendicular ray (oblique distance will give fisheye effect!)    
			if (side == 0) perpWallDist = fabs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX);
			else       perpWallDist = fabs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY);
			 //Calculate height of line to draw on screen       
     		 int lineHeight = abs(int(h / perpWallDist));
     		 
     		 //calculate lowest and highest pixel to fill in current stripe
     		 drawStart = -lineHeight / 2 + h /2;
     		 if(drawStart < 0) drawStart = 0;
     		 drawEnd = lineHeight / 2 + h / 2;
     		 if(drawEnd >= h) drawEnd = h - 1;
     		 //texturing calculations
     		 int texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
     		 if (!opposite)
			 {	 
     		 //calculate value of wallX
     		 if (side == 1) wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX;
     		 else       wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY;
			 }
			 else
			 {
				if (side == 1) wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / -rayDirY) * -rayDirX;
     			else       wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / -rayDirX) * -rayDirY;
			 }
     		 wallX -= floor((wallX));
     		  
     		 //x coordinate on the texture
	 		 int wall_light=0;
     		 int texX = int(wallX * double(texWidth));
     		 if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
     		 if(side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
			 if (!opposite)
			 {
	 		 if (rayDirX > 0 && side == 0) wall_light = 255 / (8-lightMap [(int)mapX-1][(int)mapY]);
	 		 if (rayDirX < 0 && side == 0) wall_light = 255 / (8-lightMap [(int)mapX+1][(int)mapY]);
	 		 if (rayDirY > 0 && side == 1) wall_light = 255 / (8-lightMap [(int)mapX][(int)mapY-1]);
	 		 if (rayDirY < 0 && side == 1) wall_light = 255 / (8-lightMap [(int)mapX][(int)mapY+1]);
			 }
			 else if (opposite) wall_light = 255 / (8-lightMap [(int)mapX][(int)mapY]);
     		 for(int y = drawStart; y < drawEnd; y++)
     			{
					if (ZBuffer[x][y] > perpWallDist || ZBuffer[x][y] == 0) //We can draw.
					{
     				int d = y * 256 - h * 128 + lineHeight * 128; //256 and 128 factors to avoid floats
     			 	int texY = ((d * texHeight) / lineHeight) / 256;
     				int color = texture[texNum][texWidth * texY + texX];
     				if (color > 0)
						{
							color = MixColorAlpha (color,GetColor565(0,0,10),wall_light);
							buffer[y][x] = color;
							//SET THE ZBUFFER FOR THE SPRITE CASTING
							ZBuffer[x][y] = perpWallDist; //perpendicular distance is used
						}
					else
						{
							//We found transparency, we have to draw deeper.
							deeper = true;
							hit = 0;
						}
					}
     			}
			 if (opposite)
			 {
				 oppositedrawn = true;
				 if (deeper) opposite = false;
				 rayDirX = rayDirX * (-1);
				 rayDirY = rayDirY * (-1);
				 stepX = stepX * (-1);
				 stepY = stepY * (-1);
			 }
			 else if (!opposite && deeper && (prevmapX != mapX && prevmapY != mapY))
			 {
				 opposite = true;
				 prevmapX = mapX;
				 prevmapY = mapY;
			 }
			//End of wall drawing functions.
			}
		//End of loop.
		}
      
      //FLOOR CASTING
      double floorXWall, floorYWall; //x, y position of the floor texel at the bottom of the wall
      //4 different wall directions possible
      if(side == 0 && rayDirX > 0)
      {
        floorXWall = mapX;
        floorYWall = mapY + wallX;
		if (opposite) floorXWall = floorXWall + 1.0;
      }
      else if(side == 0 && rayDirX < 0)
      {
        floorXWall = mapX + 1.0;
        floorYWall = mapY + wallX;
		if (opposite) floorXWall = floorXWall - 1.0;
      }
      else if(side == 1 && rayDirY > 0)
      {
        floorXWall = mapX + wallX;
        floorYWall = mapY;
		if (opposite) floorYWall = floorYWall + 1.0;
      }
      else
      {
        floorXWall = mapX + wallX;
        floorYWall = mapY + 1.0;
		if (opposite) floorYWall = floorYWall - 1.0;
      }
      
      double distWall, distPlayer, currentDist;

      distWall = perpWallDist;
      distPlayer = 0.0;
      if (drawEnd < 0) drawEnd = h; //becomes < 0 when the integer overflows
      //draw the floor from drawEnd to the bottom of the screen
      for(int y = drawEnd; y < h; y++)
      {
        currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
		if (currentDist < ZBuffer[x][y] || ZBuffer[x][y] == 0)
		{
			double weight = (currentDist - distPlayer) / (distWall - distPlayer);
         
			double currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
			double currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
        
			int floorTexX, floorTexY;
			int lighting = 255 / (8-lightMap [(int)currentFloorX][(int)currentFloorY]);
			floorTexX = int(currentFloorX * texWidth) % texWidth;
			floorTexY = int(currentFloorY * texHeight) % texHeight; 
        
			//floor
			buffer[y][x] = MixColorAlpha(texture[3][texWidth * floorTexY + floorTexX],GetColor565(0,0,10),lighting);
			//ceiling (symmetrical!)
			buffer[h-y][x] = MixColorAlpha(texture[6][texWidth * floorTexY + floorTexX],GetColor565(0,0,10),lighting);
			//SET THE ZBUFFER FOR THE SPRITE CASTING
			ZBuffer[x][y] = currentDist; //perpendicular distance is used
			ZBuffer[x][h-y] = currentDist; //perpendicular distance is used
		}
      }
    }
    
	

    //SPRITE CASTING
    //sort sprites from far to close

    for(int i = 0; i < numSprites; i++)
    {
      spriteOrder[i] = i;
      spriteDistance[i] = ((posX - sprite[i].x) * (posX - sprite[i].x) + (posY - sprite[i].y) * (posY - sprite[i].y)); //sqrt not taken, unneeded
    }
    combSort(spriteOrder, spriteDistance, numSprites);
     
    //after sorting the sprites, do the projection and draw them
    for(int i = 0; i < numSprites; i++)
    {
      //translate sprite position to relative to camera
      double spriteX = sprite[spriteOrder[i]].x - posX;
      double spriteY = sprite[spriteOrder[i]].y - posY;
         
      //transform sprite with the inverse camera matrix
      // [ planeX   dirX ] -1                                       [ dirY      -dirX ]
      // [               ]       =  1/(planeX*dirY-dirX*planeY) *   [                 ]
      // [ planeY   dirY ]                                          [ -planeY  planeX ]
      
      double invDet = 1.0 / (planeX * dirY - dirX * planeY); //required for correct matrix multiplication
      
      double transformX = invDet * (dirY * spriteX - dirX * spriteY);
      double transformY = invDet * (-planeY * spriteX + planeX * spriteY); //this is actually the depth inside the screen, that what Z is in 3D       
            
      int spriteScreenX = int((w / 2) * (1 + transformX / transformY));
      
      //parameters for scaling and moving the sprites
      #define uDiv 1
      #define vDiv 1
      #define vMove 0.0
      int vMoveScreen = int(vMove / transformY);
      
      //calculate height of the sprite on screen
      int spriteHeight = abs(int(h / (transformY))) / vDiv; //using "transformY" instead of the real distance prevents fisheye
      //calculate lowest and highest pixel to fill in current stripe
      int drawStartY = -spriteHeight / 2 + h / 2 + vMoveScreen;
      if(drawStartY < 0) drawStartY = 0;
      int drawEndY = spriteHeight / 2 + h / 2 + vMoveScreen;
      if(drawEndY >= h) drawEndY = h - 1;
      
      //calculate width of the sprite
      int spriteWidth = abs( int (h / (transformY))) / uDiv;
      int drawStartX = -spriteWidth / 2 + spriteScreenX;
      if(drawStartX < 0) drawStartX = 0;
      int drawEndX = spriteWidth / 2 + spriteScreenX;
      if(drawEndX >= w) drawEndX = w - 1;
      int spr_light = 255 / (8-lightMap [(int)sprite[spriteOrder[i]].x][(int)sprite[spriteOrder[i]].y]);
      //loop through every vertical stripe of the sprite on screen
      for(int stripe = drawStartX; stripe < drawEndX; stripe++)
      {
        int texX = int(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth / spriteWidth) / 256;
        //the conditions in the if are:
        //1) it's in front of camera plane so you don't see things behind you
        //2) it's on the screen (left)
        //3) it's on the screen (right)
        //4) ZBuffer, with perpendicular distance
        if(transformY > 0 && stripe > 0 && stripe < w) 
        for(int y = drawStartY; y < drawEndY; y++) //for every pixel of the current stripe
        {
		  if (transformY < ZBuffer[stripe][y])
		  {
			int d = (y-vMoveScreen) * 256 - h * 128 + spriteHeight * 128; //256 and 128 factors to avoid floats
			int texY = ((d * texHeight) / spriteHeight) / 256;
			unsigned char color = texture[sprite[spriteOrder[i]].texture][texWidth * texY + texX]; //get current color from the texture
			if (color !=0) 
			{
				  color = MixColorAlpha (color,GetColor565(0,0,10),spr_light);
				  buffer[y][stripe] = color; //paint pixel if it isn't black, black is the invisible color
		  }
		  }
        }
      }
	  
    }
	engine->ReleaseBitmapSurface (screen);
	engine->NotifySpriteUpdated (slot);
}


EDIT (again): Solved that. I should have this engine ready really soon!


SMF spam blocked by CleanTalk