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

https://i.sstatic.net/oThEZeXA.png



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *