JSFiddle - React, Tailwind, and code Playground

by Ben Gillbanks

HTML

<canvas id="canvas" width="600" height="600"></canvas>

CSS

canvas {
			background: #000;
			display: block;
			margin: 0 auto;
		}

JavaScript

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;

const fov = 90;
const fovRad = 1 / Math.tan((fov * 0.5) * Math.PI / 180);
const cameraPos = { x: 0, y: 0, z: 0 };

function project(point) {
	// point.z should be > 0 (after translation) to be in front of the camera.
	return {
		x: (point.x * fovRad / point.z) * width + width / 2,
		y: (-point.y * fovRad / point.z) * height + height / 2
	};
}

const cubeVertices = [
	{ x: -1, y: -1, z: -1 },
	{ x:  1, y: -1, z: -1 },
	{ x:  1, y:  1, z: -1 },
	{ x: -1, y:  1, z: -1 },
	{ x: -1, y: -1, z:  1 },
	{ x:  1, y: -1, z:  1 },
	{ x:  1, y:  1, z:  1 },
	{ x: -1, y:  1, z:  1 }
];

const cubeFaces = [
	[0, 1, 2, 3], // back
	[4, 5, 6, 7], // front
	[0, 1, 5, 4], // bottom
	[2, 3, 7, 6], // top
	[1, 2, 6, 5], // right
	[0, 3, 7, 4]  // left
];

function rotateX(point, angle) {
	const cos = Math.cos(angle);
	const sin = Math.sin(angle);
	return {
		x: point.x,
		y: point.y * cos - point.z * sin,
		z: point.y * sin + point.z * cos
	};
}

function rotateY(point, angle) {
	const cos = Math.cos(angle);
	const sin = Math.sin(angle);
	return {
		x: point.x * cos + point.z * sin,
		y: point.y,
		z: -point.x * sin + point.z * cos
	};
}

function vectorSubtract(a, b) {
	return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
}

function vectorAdd(a, b) {
	return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
}

function vectorScale(v, s) {
	return { x: v.x * s, y: v.y * s, z: v.z * s };
}

function crossProduct(a, b) {
	return {
		x: a.y * b.z - a.z * b.y,
		y: a.z * b.x - a.x * b.z,
		z: a.x * b.y - a.y * b.x
	};
}

function dotProduct(a, b) {
	return a.x * b.x + a.y * b.y + a.z * b.z;
}

function normalize(v) {
	const length = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
	return { x: v.x/length, y: v.y/length, z: v.z/length };
}

let angle = 0;
const lightDir = normalize({ x: 1, y: 1, z: -1 });

function draw() {
	ctx.clearRect(0, 0, width,...