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.
let’s get into the core concepts of raycasting and the math behind making a 3d scene work.
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 is what makes a 2d grid look 3d. the idea? fire rays in different directions and see where they hit walls. that’s it.
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.
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 raycasting is a pain, but here’s how to keep your sanity:
take it step by step, debug as you go, and eventually, you’ll have a working raycasting engine.