home

dt: 28/02/2024

Notes on raycasting

heard of john carmack? i suppose you've read *masters of doom* too. well, who doesn’t know the creator of *doom*, john carmack? that book introduced me to raycasting, and ever since, i’ve been a fan. recently, i stumbled upon this insane tutorial on implementing raycasting in c++ (https://lodev.org/cgtutor/raycasting.html). even now, i can’t fully wrap my head around how games like *wolfenstein 3d* managed to fake a 3d world inside a 2d grid. it's unreal.

a couple of days later, i thought—why not build a basic raycasting engine, but in javascript? now, why javascript? good question. if you’ve ever brought up javascript around me, you already know how i feel about it. hate is a mild word. so why use it? simple: everyone has a browser, and i wanted people to mess around with it without hassle.

i won’t go into the code here (it’s on github, or will be by the time i finish writing this). what i want to talk about is the math and physics that blew my mind while working on it. honestly, if i had realized how useful this stuff was back in high school, i’d have paid more attention.

a while ago, when i was playing around with an arcade-style shooter in pygame, i used the distance formula for collision detection. seems basic, but i know my 15-year-old self—who was already deep into computers—would have been blown away.

now, how hard could making 2.5d work be? i mean, just drawing on a 2d canvas and making it feel 3d? well, for me, it was... a task. i am not a smart man. you might want to try it for yourself.

there's a lot still to do. you can try the output at raycast. these are rough notes i take for future reference or anyone interested in raycasting or application of mathematics. i'd still recommend you to read it, i had fun writing it.

i strongly recommend reading Lode's Computer Graphics Tutorial to get better idea of what comes here.

understanding the mathematics and logic

let’s get into the core concepts of raycasting and the math behind making a 3d scene work.

player movement

moving the player is just basic trig. imagine you’re on a grid, moving forward, backward, and rotating left or right. your position and direction can be calculated with simple math:

moving forward is just using cosine and sine on your angle: newX = player.x + Math.cos(player.angle) * player.speed; and newY = player.y + Math.sin(player.angle) * player.speed;. moving backward? same thing, just subtracted. turning is just player.angle -= player.turnSpeed; for left and player.angle += player.turnSpeed; for right.

raycasting logic

raycasting is what makes a 2d grid look 3d. the idea? fire rays in different directions and see where they hit walls. that’s it.

step-by-step explanation

start the ray at the player’s position: let rayX = player.x;, let rayY = player.y;. get the direction: const rayDirX = Math.cos(angle); and const rayDirY = Math.sin(angle);.

then, loop forward in small steps: rayX += rayDirX * stepSize;, rayY += rayDirY * stepSize;.

check if it hit a wall: const mapX = Math.floor(rayX);, const mapY = Math.floor(rayY);. if it did, calculate the distance: const distance = Math.sqrt((player.x - rayX)^2 + (player.y - rayY)^2);. repeat until you hit something.

rendering the scene

once we have distances, we draw walls. first, clear the canvas: ctx.fillStyle = '#000';, ctx.fillRect(0, 0, canvas.width, canvas.height);.

set the field of view: const FOV = Math.PI / 3;, then cast a ray for each column of the screen. each ray’s angle: const rayAngle = player.angle - FOV / 2 + (i / rayCount) * FOV;. get the wall height: const wallHeight = (canvas.height / distance) * 0.5;, then draw it: ctx.fillStyle = '#FF0000';, ctx.fillRect(i, (canvas.height - wallHeight) / 2, 1, wallHeight);.

that’s how you fake 3d from a 2d grid.

i have a lot on my plate, but what's life if not fun. i am working on promocraft, but my next goal is to add textures. you may find the code on github.

debugging and refining

debugging raycasting is a pain, but here’s how to keep your sanity:

  1. check initialization: make sure the canvas and context are working.
  2. verify movement: log player coordinates, make sure they update correctly.
  3. debug raycasting: log ray positions, check if they detect walls properly.
  4. test rendering: make sure wall heights actually make sense.

take it step by step, debug as you go, and eventually, you’ll have a working raycasting engine.