Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created May 14, 2025 02:54
Show Gist options
  • Save EncodeTheCode/5a8e2144a8dbc7762bc3e008fc68ae56 to your computer and use it in GitHub Desktop.
Save EncodeTheCode/5a8e2144a8dbc7762bc3e008fc68ae56 to your computer and use it in GitHub Desktop.
<canvas id="canvas" width="800" height="600"></canvas>
<script>
// Basic 3D Vector class
class Vector3 {
constructor(x = 0, y = 0, z = 0) {
this.x = x; this.y = y; this.z = z;
}
add(v) { return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z); }
subtract(v) { return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z); }
rotateY(angle) {
const cos = Math.cos(angle), sin = Math.sin(angle);
return new Vector3(this.x * cos - this.z * sin, this.y, this.x * sin + this.z * cos);
}
}
// Simple Camera
class Camera {
constructor(pos, rotY = 0) {
this.pos = pos;
this.rotY = rotY;
}
move(forward, strafe) {
const dx = Math.cos(this.rotY) * forward - Math.sin(this.rotY) * strafe;
const dz = Math.sin(this.rotY) * forward + Math.cos(this.rotY) * strafe;
this.pos.x += dx;
this.pos.z += dz;
}
project(v, w, h, fov = 100, depth = 5) {
const dir = v.subtract(this.pos).rotateY(-this.rotY);
const z = dir.z + depth;
const factor = fov / z;
return {
x: dir.x * factor + w / 2,
y: -dir.y * factor + h / 2,
depth: z
};
}
}
// Box Mesh Generator
function createBox(size = 1, center = new Vector3(), color = '#ccc') {
const s = size / 2;
const v = [
new Vector3(-s, -s, -s), new Vector3(s, -s, -s),
new Vector3(s, s, -s), new Vector3(-s, s, -s),
new Vector3(-s, -s, s), new Vector3(s, -s, s),
new Vector3(s, s, s), new Vector3(-s, s, s),
].map(p => p.add(center));
const faces = [
[0,1,2,3], [4,5,6,7],
[0,1,5,4], [2,3,7,6],
[0,3,7,4], [1,2,6,5],
];
return { vertices: v, faces, color };
}
// Setup
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const camera = new Camera(new Vector3(0, 1, -5));
const scene = [];
// Room: floor, ceiling, walls (shaded gray)
scene.push(createBox(20, new Vector3(0, -5.5, 0), '#555')); // Floor
scene.push(createBox(20, new Vector3(0, 5.5, 0), '#666')); // Ceiling
scene.push(createBox(20, new Vector3(0, 0, -10.5), '#777')); // Back wall
scene.push(createBox(20, new Vector3(0, 0, 10.5), '#888')); // Front wall
scene.push(createBox(20, new Vector3(-10.5, 0, 0), '#999')); // Left wall
scene.push(createBox(20, new Vector3(10.5, 0, 0), '#aaa')); // Right wall
// Add boxes on the floor
for (let x = -3; x <= 3; x += 2) {
for (let z = -3; z <= 3; z += 2) {
scene.push(createBox(1, new Vector3(x, -4.5, z), '#0ff'));
}
}
// Controls
const keys = {};
window.addEventListener('keydown', e => keys[e.key.toLowerCase()] = true);
window.addEventListener('keyup', e => keys[e.key.toLowerCase()] = false);
// Renderer
function render() {
if (keys['w']) camera.move(0.1, 0);
if (keys['s']) camera.move(-0.1, 0);
if (keys['a']) camera.move(0, 0.1);
if (keys['d']) camera.move(0, -0.1);
if (keys['arrowleft']) camera.rotY -= 0.03;
if (keys['arrowright']) camera.rotY += 0.03;
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (const mesh of scene) {
const projected = mesh.vertices.map(v => camera.project(v, canvas.width, canvas.height));
for (const face of mesh.faces) {
const faceVerts = face.map(i => projected[i]);
if (faceVerts.every(p => p.depth > 0)) {
ctx.beginPath();
ctx.moveTo(faceVerts[0].x, faceVerts[0].y);
for (let i = 1; i < faceVerts.length; i++) {
ctx.lineTo(faceVerts[i].x, faceVerts[i].y);
}
ctx.closePath();
ctx.fillStyle = mesh.color;
ctx.fill();
ctx.strokeStyle = '#000';
ctx.stroke();
}
}
}
requestAnimationFrame(render);
}
render();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment