// The code is split up to easily understand for new programmers to 3D math and calculations. It is complete possible to compact this code
// into a simpler version, if you'd prefer that you can do that on your own.
// This javascript file has helpful comments to teach you how to do simple 3D math calculations in js.
// If you'd like a step by step tutorial, follow this link: https://pages.opencodingsociety.com/navigation/documentation/tutorial/intro
// Setup for context (ctx) and canvas
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d'); // This code will work with a 2d screen so select 2d (kinda confusing I know)
const halfWidth = canvas.width / 2; // Very important for later
const halfHeight = canvas.height / 2;
// Global Variable Setup
const vector = {
x: 0,
y: 0,
z: 0
};
// To keep code organized I'd recommend keeping items related to one another in objects, for example camera values should be in a camera object
const camera = {
x: 0, // camera x position
y: 0, // camera y position
z: 0, // camera z position
FOV: 90, // camera FOV (Field Of View)
direction : {
x: 0, // camera direction on x axis
y: 0, // camera direction on y axis
z: 0 // camera direction on z axis
}
};
// I'd recommend memorizing this equation but you don't want to, I'd recommend keeping it at 1
const focalLength = 1 / Math.tan(toRadians(camera.FOV) / 2); // Focal length just typically means what you can see from the camera's point of view
function toRadians(val) {
// if you like working with degrees then this function is required, since javascript only takes radians for angles
return val * (Math.PI / 180)
};
function Vector3(x,y,z) {
// A Vector3 is just a form of 3D coordinates to hold values
// A Vector3 holds 3 spacial coordinates while a Vector2 only holds 2 spacial coordinates
// To keep code clean, this function will just be used to update the vector values
vector.x = x;
vector.y = y;
vector.z = z;
};
// Step 1: Translation
function translate(x,y,z) {
// This function translates a Vector3 in the opposite position of the camera
// Think of it as if on a graph the camera becomes the origin
// This also has the effect of making the coordinates move in the opposite direction of the camera
const tempX = x - camera.x;
const tempY = y - camera.y;
const tempZ = z - camera.z;
Vector3(tempX, tempY, tempZ);
};
// Step 2: Rotation
// Part A
function rotateX(x,y,z) {
// This function rotates a Vector3 in the direction of the camera x direction
// Think of it as if the Vector3 is spinning around the camera (origin) based off the camera angle
// When rotating on an axis, you only need to affect the coordinate you're rotating around (in this case the X axis) and the z coordinate
const angle = toRadians(camera.direction.x);
const tempX = x * Math.cos(angle) - z * Math.sin(angle);
const tempZ = x * Math.sin(angle) + z * Math.cos(angle);
Vector3(tempX, y, tempZ);
};
// Part B
function rotateY(x,y,z) {
// This function rotates a Vector3 in the direction of the camera y direction
// This function is carbon copy of the rotateY function except it rotates a Vector3 around the Y Axis
const angle = toRadians(camera.direction.y);
const tempY = y * Math.cos(angle) - z * Math.sin(angle);
const tempZ = y * Math.sin(angle) + z * Math.cos(angle);
Vector3(x, tempY, tempZ);
};
// Part C
function rotateZ(x,y,z) {
// This function rotates a Vector3 in the direction of the camera z direction
// This function is different due to it rotating the x and y coordinates and not the z coordinate
const angle = toRadians(camera.direction.z);
const tempX = x * Math.cos(angle) - y * Math.sin(angle);
const tempY = x * Math.sin(angle) + y * Math.cos(angle);
Vector3(tempX, tempY, z);
};
// Step 3: Projection
// Imagine projection like a project that shows an 2D image on a wall, the project actually launches light in a 3D space to create a 2D image
function project3DTo2D(x,y,z) {
// This function flattens 3D spacial coordinates into 2D spacial coordinates
// In more simple terms, it converts 3D coordinates in a world to 2D onscreen coordinates
const tempX = halfWidth + halfWidth * focalLength * (x / z);
const tempY = halfHeight + halfWidth * focalLength * (y / z); // You can replace halfWidth with halfHeight but your output will look squashed
return {x: tempX, y: tempY}; // we can return this value rather than making it a Vector3 since it is the last function required
};
// Step 4: Final Function
function goTo(x,y,z) {
translate(x,y,z); // Step 1
rotateX(vector.x, vector.y, vector.z); // Step 2: Part A
rotateY(vector.x, vector.y, vector.z); // Step 2: Part B
rotateZ(vector.x, vector.y, vector.z); // Step 2: Part C
return project3DTo2D(vector.x, vector.y, vector.z); // Step 3
};
// Congratulions!!!
// You completed the heavy load of simple 3D math!
// Don't worry it gets far worse :D (just kidding you'll be alright)