Read. Observe. Learn. :-) DCW Endeavors

JavaScript 3D Rendering Engine

In my pursuit to learn more about 3D programming, I followed this youtube tutorial, Vanilla Javascript 3D cube, by dionyziz, to produce a JavaScript 3D rendering engine for a cube. I have also completed the other video tutorial of the two videos, which involves creating solid colored sides on the cube.

UPDATE 1 2 22 - Controls have been added to adjust the speed and direction of rotation. You also can now view the code for this program below.

UPDATE 9 7 19 - I added dropdowns to change the dot colors, the background color, the viewing distance of the cube, the number of dots per edge on the cube and the number of dots in general. I also added a reload button and a pause button. I still plan on adding variations of rotation soon. I made these changes so variances of the cube can be seen. Thank you for checking this out! :-)



Change number of dots per edge:

Change color of dots:

Change background color:

Change viewing distance of cube (essentially, the placement of the camera's eye):

Add more dots to cube (does so in a random manner - can only add more, cannot lessen dot amounts):

Change speed of rotation via dtheta:

Alter magnitude/direction of x-axis rotation:

Alter magnitude/direction of y-axis rotation:

Here is the javascript code for this program along with my comments. Feel free to use this code and if you add some other cool functions to change things up, let me know! I would be interested to see what you accomplished.


var W = 600, H = 600; //variables for size of canvas
var step = {amnt: 0.5};
var MODEL_MIN_X = -2, MODEL_MAX_X = 2;
var MODEL_MIN_Y = -2, MODEL_MAX_Y = 2;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var points = []; //points array instantiation

var theta = 0;//start point for theta angle
/*The values below all can influence speed of rotation - larger values increase speed*/
var dtheta = 0.01;//MAIN CONTROL OF ROTATION SPEED 
var yRotate = 1.0;//controls rotation around y axis 
var xRotate = 0.5;//controls rotation around x axis 

//listens for x_rotation update
document.getElementById('x_rotation').addEventListener('change',xRotation);

//changes the xRotate value
function xRotation(){
	var x = document.getElementById('x_rotation').value;
	xRotate = x*1.0; //multiplied by 1.0 to force decimal number usage
}

//listens for y_rotation update
document.getElementById('y_rotation').addEventListener('change', yRotation);

//changes the yRotate value
function yRotation(){
	var y = document.getElementById('y_rotation').value;
	yRotate = y*1.0;
}

//listens for dtheta change or update
document.getElementById('speed_rotation').addEventListener('change',speedRotation);

//changes the value of dtheta
function speedRotation(){
	var speed = document.getElementById('speed_rotation').value;
	dtheta = speed*1.0;	
}

//listens for update of edgedots value
document.getElementById('edge_dots').addEventListener('change', numEdgeDots);

//update number of points along edge
function numEdgeDots(){
    
    var edgedots = document.getElementById('edge_dots').value;
    step.amnt = edgedots*1.0;//Multiply by 1.0 to make sure decimal num
    points = [];
    initGeometry();
}


document.getElementById('edge_dots2').addEventListener('change', numEdgeDots2);
function numEdgeDots2(){
    
    var edgedots2 = document.getElementById('edge_dots2').value;
    step.amnt = edgedots2*1.0;
    initGeometry();
}

//default values on page opening, for distance, color, and background color
var perspective_distance = {amnt: 2.5};
var color = {colordots: 'white'};
var background_color = {backcolor: 'black'};

//all the addEventListener below listen for changes in corresponding id:
document.getElementById('color_change2').addEventListener('change', colorBackground);

document.getElementById('color_change').addEventListener('change', colorDots);

document.getElementById('view_distance').addEventListener('change', perspectiveDistance);

function perspectiveDistance(){
    var x = document.getElementById('view_distance').value;
    perspective_distance.amnt = x*1.0; 
}

function pauseProgram(){
    alert("Program paused. Click OK to continue.");
}

function colorDots(){
    //alert("stop");
    var col = document.getElementById('color_change').value;
    color.colordots = col;
}

function colorBackground(){
    var col_b = document.getElementById('color_change2').value;
    background_color.backcolor = col_b;
}


function theParameters(){ 
    //perspective_distance.amnt = 2.5;
    step.amnt = 0.3;
}

//vars were here previously, but moved them up to top of page.  

//1. this function gives numbers for x,y,z in points[]
function initGeometry(){
	for(var x = -1; x <= 1; x += step.amnt){
		for(var y = -1; y <= 1; y += step.amnt){
			for(var z = -1; z <= 1; z += step.amnt){
				points.push([x,y,z]);
			}
		}
	}
}

//5. 
function perspectiveProjection(point){
	var x = point[0],
		y = point[1],
		z = point[2];
		
	return [//i believe this is the homogeneous effect he's doing here
		x/(z + perspective_distance.amnt),//z + 1 is distance from camera to viewing screen
		y/(z + perspective_distance.amnt)
	];
}


//4. 
function project(point){//this function takes coordinates from the model space, which is the 3D space, 
//and projects them into 2dimension space - this should also remove the z component 
//because only x,y shows on 2D canvas
//so basically takes 3d point and makes it 2dimension
	var perspectivePoint = perspectiveProjection(point);
	var x = perspectivePoint[0],
		y = perspectivePoint[1];
	return [
		//this below takes from -2 to 2 and gives value of from 0 to min or max canvas dimensions
		W*(x - MODEL_MIN_X)/(MODEL_MAX_X - MODEL_MIN_X),
		H*(1 - (y - MODEL_MIN_Y)/(MODEL_MAX_Y - MODEL_MIN_Y))//1- added so to convert canvas y direction 
		//to 3d y direction (positive up)
	];
	
}

//3. this function is used by render() and 
//gets help from project(point)
function renderPoint(point){//this function takes a point and draws it
	var projectedPoint = project(point);
	var x = projectedPoint[0],
		y = projectedPoint[1];
	ctx.beginPath();
	ctx.moveTo(x,y);
	ctx.lineTo(x + 2, y + 2);//both these were 1
	ctx.lineWidth = 2;//this was 4
	ctx.strokeStyle = color.colordots;//this was white
	ctx.stroke();
	
}

//2.5. row_box
function rotateY(point, theta){
	var x = point[0],
		y = point[1],
		z = point[2];
	return [
		Math.cos(theta)*x - Math.sin(theta)*z,//this is new x value
		y,//this is same y value as previous
		Math.sin(theta)*x + Math.cos(theta)*z//this is the new z value
	];	
}

//2.5. row_box
function rotateX(point, theta){
	var x = point[0],
		y = point[1],
		z = point[2];
	return [
		x,
		Math.cos(theta)*y + Math.sin(theta)*z,//this is new x value
		//this is same y value as previous
		-Math.sin(theta)*y + Math.cos(theta)*z//this is the new z value
	];
}
/*
//original values:
var theta = 0;
var dtheta = 0.01; */



//2. this function puts points on canvas, with
//help from other functions
function render(){
	ctx.fillStyle = background_color.backcolor;//was black
	ctx.fillRect(0,0,W,H);
	//ctx.clearRect(0, 0, W, H);//x value, y value, width of canvas, height of canvas
	theta += dtheta;
	points.forEach((point) => {
		//point = rotateY(point, theta);//original one
		point = rotateY(point, yRotate*theta);//HIGHER NUMBER MULTIPLIED BY THETA MEANS THIS IS PRIMARY AXIS OF ROTATION
		//point = rotateX(point, 0.43*theta);//original one
		point = rotateX(point, xRotate*theta);//LOWER NUMBER MULTIPLIED BY THETA IS LESSER AXIS OF ROTATION
		renderPoint(point);//will have x,y,z
	});
	
	requestAnimationFrame(render);//this will call render function again
	//NOTE: requestAnimationFrame is a js window doc function
	//see here: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
	//this function must be placed within the function calling it - recursive function? 
}

function refreshPage(){
       window.location.reload();
}

initGeometry();//call this first
render(); //then call this function