Issue with converting Sprite’s world cords into screen cords in my custom raycaster
I am making a ray casting engine with Raylib and I have come across an issue with getting the screen coordinates of the sprite. The code for the sprite handling is as follows.
void draw_sprites(){
sprite_t sprite = sprites[0];
//translate points to players origin
float dx = sprite.x - player.x;
float dy = sprite.y - player.y;
float dz = sprite.z;
float pa_radians = DEGREE_TO_RADIANS(player.pa);
float pa_sin = sinf(pa_radians);
float pa_cos = cosf(pa_radians);
// rotated around players angle
float a=dy*pa_cos+dx*pa_sin;
float b=dx*pa_cos-dy*pa_sin;
// 3d projection onto the screen
float screen_x = (a /b)+(project.half_width);
float screen_y = (dz / b)+(project.half_height);
#ifdef DEBUG
printf("dx = %f, dy = %f a = %f b = %f, screen_y = %f, screen_x %f\n",dx,dy,a,b,screen_y,screen_x);
#endif
// for now I am just drawing a rectangle, until I get this sorted out.
ImageDrawRectangleV(&FrameBuffer, (Vector2){.x = screen_x,.y = screen_y}, (Vector2){.x = 100,.y = 100}, YELLOW);
}
My understanding of the math isn’t the best, but I know my output from this is not accurate, As a sprites aren’t really locked to their map position on the screen.I have logged some data from the output of the math I am doing and it is as follows
dx = 0.500000, dy = 0.000000 a = 0.000000 b = 0.500000, screen_y = 360.000000, screen_x 640.000000
player x = 1.500000 player y = 1.500000, player.pa = 0.000000
//one result at 85
player x = 1.500000 player y = 1.500000, player.pa = 85.500000
sprite x = 2.000000 sprite y = 1.500000 sprite_z = 0.000000
dx = 0.500000, dy = 0.000000 a = 0.498459 b = 0.039230, screen_y = 360.000000, screen_x 652.706177
// another result at 275.00 degrees
dx = 0.500000, dy = 0.000000 a = -0.497698 b = 0.047923, screen_y = 360.000000, screen_x 629.614624
player x = 1.500000 player y = 1.500000, player.pa = 275.500000
sprite x = 2.000000 sprite y = 1.500000 sprite_z = 0.000000
Thanks to input from DMGregory, I forgot to mention that the players FOV is fixed to 90 degrees as well, at his request, I decided to also provide the wall and floor casting code.
//FrameBuffer is just a bitmap that get's updated and sent to the gpu
Image FrameBuffer;
Texture FrameText;
// this struct is just the data for the screen projection
// it gets scaled to the screen's resolution of 1280x720p
// if it's not already the same resolution
struct{
int width;
int height;
int half_height;
int half_width;
}project = {0};
// simple DDA raycasting
void raycast(float ray_angle,float raySin, float rayCos,float cosDelta, float sinDelta){
for(int ray_cnt = 0;ray_cnt < project.width;ray_cnt++){
int wall = 0;
float delta_dist_x = fabs(1 / rayCos);
float delta_dist_y = fabs(1/ raySin);
float side_dist_x;
float side_dist_y;
int step_x;
int step_y;
int mapx = (int)player.x;
int mapy = (int)player.y;
int side;
if(rayCos < 0){
step_x = -1;
side_dist_x = (player.x - (float)mapx) *delta_dist_x;
}else{
step_x = 1;
side_dist_x = ((float)mapx + 1.0 - player.x) * delta_dist_x;
}
if(raySin < 0){
step_y = -1;
side_dist_y = (player.y - (float)mapy) *delta_dist_y;
}else{
step_y = 1;
side_dist_y = ((float)mapy + 1.0 - player.y) * delta_dist_y;
}
while(!wall){
if(side_dist_x < side_dist_y){
side_dist_x += delta_dist_x;
mapx += step_x;
side = 0;
}else{
side_dist_y += delta_dist_y;
mapy += step_y;
side = 1;
}
wall = worldMap[mapy][mapx].type;
}
cell_t wall_cell = worldMap[mapy][mapx];
float dist;
float hitx;
if(side == 0){
dist = (side_dist_x - delta_dist_x);
hitx = player.y + (dist * raySin);
}
else{
dist = (side_dist_y - delta_dist_y);
hitx = player.x + (dist * rayCos);
}
hitx -= floorf(hitx);
float wall_height = (float)project.half_height / dist;
Vector2 wall_pos = {.x = ray_cnt, .y = project.half_height - wall_height};
texture_t texture = loaded_textures[wall_cell.texture_id];
int texture_pos = ((int)(texture.width * hitx) & (texture.width - 1));
if(!enable_skybox){
draw_ceiling(ray_cnt, wall_height, rayCos, raySin);
}
draw_texture(wall_pos,texture_pos,wall_height,texture);
draw_floor(ray_cnt, wall_height, rayCos, raySin);
float new_cos = rayCos * cosDelta - raySin * sinDelta;
raySin = raySin * cosDelta + rayCos * sinDelta;
rayCos = new_cos;
ray_angle += rc_data.increment_angle;
zbuffer[ray_cnt] = dist;
}
// If this doesn't make it apparent I do convert the textures
// to column major order
void draw_floor(int x,float wall_height,float rayCos,float raySin){
float start = project.half_height + wall_height;
float y = start;
for(;y < project.height;){
float dist = project.height / (2 * y - project.height);
float tilex = dist * rayCos;
float tiley = dist * raySin;
tilex += player.x;
tiley += player.y;
cell_t floor_cell = worldMap[(int)tiley][(int)tilex];
texture_t floor_text = loa[![ded][1]][1]_textures[floor_cell.texture_id];
int text_x = (int)(tilex * floor_text.width) & (floor_text.width - 1);
int text_y = (int)(tiley * floor_text.height) & (floor_text.height - 1);
ImageDrawPixelV(&FrameBuffer,(Vector2){.x = x,.y = y},floor_text.contents[text_x * floor_text.height + text_y]);
y++;
}
}
Here is a visual example of the sprite drawing with the sprite and the player at the following values.
dx = 0.500000, dy = 0.000000 a = 0.000000 b = 0.500000, screen_y = 360.000000, screen_x 640.000000
player x = 1.500000 player y = 1.500000, player.pa = 0.000000
sprite x = 2.000000 sprite y = 1.500000 sprite_z = 0.000000
