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;
}
//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);
}