{"id":316,"date":"2026-08-05T08:39:44","date_gmt":"2026-08-05T08:39:44","guid":{"rendered":"https:\/\/x.sheep-mine.ts.net\/index.php\/issue-with-converting-sprites-world-cords-into-screen-cords-in-my-custom-raycas\/"},"modified":"2026-08-05T08:39:44","modified_gmt":"2026-08-05T08:39:44","slug":"issue-with-converting-sprites-world-cords-into-screen-cords-in-my-custom-raycas","status":"publish","type":"post","link":"https:\/\/x.sheep-mine.ts.net\/index.php\/issue-with-converting-sprites-world-cords-into-screen-cords-in-my-custom-raycas\/","title":{"rendered":"Issue with converting Sprite&#8217;s world cords into screen cords in my custom raycaster"},"content":{"rendered":"<p><br \/>\n<\/p>\n<p>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.<\/p>\n<pre><code>void draw_sprites(){\n  sprite_t sprite = sprites[0];\n  \/\/translate points to players origin\n  float dx = sprite.x - player.x;\n  float dy = sprite.y - player.y;\n  float dz = sprite.z;\n  float pa_radians = DEGREE_TO_RADIANS(player.pa);\n  float pa_sin = sinf(pa_radians);\n  float pa_cos = cosf(pa_radians);\n  \/\/ rotated around players angle\n  float a=dy*pa_cos+dx*pa_sin;\n  float b=dx*pa_cos-dy*pa_sin;\n  \/\/ 3d projection onto the screen\n  float screen_x = (a \/b)+(project.half_width);\n  float screen_y = (dz \/ b)+(project.half_height);\n  #ifdef DEBUG\n  printf(\"dx = %f, dy = %f   a = %f b = %f, screen_y = %f, screen_x %f\\n\",dx,dy,a,b,screen_y,screen_x);\n  #endif\n  \/\/ for now I am just drawing a rectangle, until I get this sorted out.\n  ImageDrawRectangleV(&FrameBuffer, (Vector2){.x = screen_x,.y = screen_y}, (Vector2){.x = 100,.y = 100}, YELLOW);\n}\n<\/code><\/pre>\n<p>My understanding of the math isn&#8217;t the best, but I know my output from this is not accurate, As a sprites aren&#8217;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<\/p>\n<pre><code>dx = 0.500000, dy = 0.000000   a = 0.000000 b = 0.500000, screen_y = 360.000000, screen_x 640.000000\nplayer x = 1.500000 player y = 1.500000, player.pa = 0.000000\n\/\/one result at 85\nplayer x = 1.500000 player y = 1.500000, player.pa = 85.500000\nsprite x = 2.000000 sprite y = 1.500000 sprite_z = 0.000000\ndx = 0.500000, dy = 0.000000   a = 0.498459 b = 0.039230, screen_y = 360.000000, screen_x 652.706177\n\/\/ another result at 275.00 degrees\ndx = 0.500000, dy = 0.000000   a = -0.497698 b = 0.047923, screen_y = 360.000000, screen_x 629.614624\nplayer x = 1.500000 player y = 1.500000, player.pa = 275.500000\nsprite x = 2.000000 sprite y = 1.500000 sprite_z = 0.000000\n<\/code><\/pre>\n<p>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.<\/p>\n<pre><code>\/\/FrameBuffer is just a bitmap that get's updated and sent to the gpu\nImage FrameBuffer;\nTexture FrameText;\n\/\/ this struct is just the data for the screen projection\n\/\/ it gets scaled to the screen's resolution of 1280x720p\n\/\/ if it's not already the same resolution \nstruct{\n  int width;\n  int height;\n  int half_height;\n  int half_width;\n}project = {0};\n\/\/ simple DDA raycasting\nvoid raycast(float ray_angle,float raySin, float rayCos,float cosDelta, float sinDelta){\n  for(int ray_cnt = 0;ray_cnt < project.width;ray_cnt++){\n    int wall = 0;\n    float delta_dist_x = fabs(1 \/ rayCos);\n    float delta_dist_y = fabs(1\/ raySin);\n    float side_dist_x;\n    float side_dist_y;\n    int step_x;\n    int step_y;\n    int mapx = (int)player.x;\n    int mapy = (int)player.y;\n    int side;\n    if(rayCos < 0){\n      step_x = -1;\n      side_dist_x = (player.x - (float)mapx) *delta_dist_x;\n    }else{\n      step_x = 1;\n      side_dist_x = ((float)mapx + 1.0 - player.x) * delta_dist_x;\n    }\n    if(raySin < 0){\n      step_y = -1;\n      side_dist_y = (player.y - (float)mapy) *delta_dist_y;\n    }else{\n      step_y = 1;\n      side_dist_y = ((float)mapy + 1.0 - player.y) * delta_dist_y;\n    }\n    while(!wall){\n      if(side_dist_x < side_dist_y){\n        side_dist_x += delta_dist_x;\n        mapx += step_x;\n        side = 0;\n      }else{\n        side_dist_y += delta_dist_y;\n        mapy += step_y;\n        side  = 1;\n      }\n      wall = worldMap[mapy][mapx].type;\n    }\n    cell_t wall_cell = worldMap[mapy][mapx];\n    float dist;\n    float hitx;\n    if(side == 0){\n      dist = (side_dist_x - delta_dist_x);\n      hitx = player.y + (dist * raySin);\n    }\n    else{\n      dist = (side_dist_y - delta_dist_y);\n      hitx = player.x + (dist * rayCos);\n    }\n    hitx -= floorf(hitx);\n    float wall_height = (float)project.half_height \/ dist;\n    Vector2 wall_pos = {.x = ray_cnt, .y = project.half_height - wall_height};\n    texture_t texture = loaded_textures[wall_cell.texture_id];\n    int texture_pos = ((int)(texture.width * hitx) &#038; (texture.width - 1));\n    if(!enable_skybox){\n      draw_ceiling(ray_cnt, wall_height, rayCos, raySin);\n    }\n    draw_texture(wall_pos,texture_pos,wall_height,texture);\n\n    draw_floor(ray_cnt, wall_height, rayCos, raySin);\n    float new_cos = rayCos * cosDelta - raySin * sinDelta;\n    raySin = raySin * cosDelta +  rayCos  * sinDelta;\n    rayCos = new_cos;\n    ray_angle += rc_data.increment_angle;\n    zbuffer[ray_cnt] = dist;\n  }\n\/\/ If this doesn't make it apparent I do convert the textures\n\/\/ to column major order\nvoid draw_floor(int x,float wall_height,float rayCos,float raySin){\n  float start = project.half_height + wall_height;\n  float y = start;\n  for(;y < project.height;){\n    float dist = project.height \/ (2 * y - project.height);\n    float tilex = dist * rayCos;\n    float tiley = dist * raySin;\n    tilex += player.x;\n    tiley += player.y;\n    cell_t floor_cell = worldMap[(int)tiley][(int)tilex];\n    texture_t floor_text = loa[![ded][1]][1]_textures[floor_cell.texture_id];\n    int text_x = (int)(tilex * floor_text.width) &#038; (floor_text.width - 1);\n    int text_y = (int)(tiley * floor_text.height) &#038; (floor_text.height - 1);\n    ImageDrawPixelV(&#038;FrameBuffer,(Vector2){.x = x,.y = y},floor_text.contents[text_x * floor_text.height + text_y]);\n    y++;\n  }\n}\n<\/code><\/pre>\n<p>Here is a visual example of the sprite drawing with the sprite and the player at the following values.<\/p>\n<pre><code>dx = 0.500000, dy = 0.000000   a = 0.000000 b = 0.500000, screen_y = 360.000000, screen_x 640.000000\nplayer x = 1.500000 player y = 1.500000, player.pa = 0.000000\nsprite x = 2.000000 sprite y = 1.500000 sprite_z = 0.000000\n<\/code><\/pre>\n<p><a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/i.sstatic.net\/oThEZeXA.png\" rel=\"nofollow noreferrer\">https:\/\/i.sstatic.net\/oThEZeXA.png<\/a><\/p>\n<p><br \/>\n<br \/><a href=\"https:\/\/gamedev.stackexchange.com\/questions\/217374\/issue-with-converting-sprites-world-cords-into-screen-cords-in-my-custom-raycas\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I am making a ray casting engine with Raylib and I have come across an&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[731,732],"tags":[],"class_list":["post-316","post","type-post","status-publish","format-standard","hentry","category-raycasting","category-raylib"],"_links":{"self":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/posts\/316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/comments?post=316"}],"version-history":[{"count":0,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/posts\/316\/revisions"}],"wp:attachment":[{"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/media?parent=316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/categories?post=316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/x.sheep-mine.ts.net\/index.php\/wp-json\/wp\/v2\/tags?post=316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}