8
\$\begingroup\$

I made a small demo in JavaScript using HTML5 canvas, which renders a rotating sphere. The sphere is generated by first creating a set amount of points (currently 50, represented by vectors) randomly, then choosing the one with the highest distance from the already generated points, so I get an even distribution, then repeating the task 100 times. Each vector is generated randomly, then normalized, and lastly, I multiply it with the radius of the circle. The rendering is done via simple canvas methods, so no WebGL is involved.

The Vector3 and Matrix3 classes are used, so I can easily rotate the ball around, they don't have anything special other than the main methods in them.

Note: I didn't want to put the code up to the internet, so I needed to put the Vector classes in the code, instructions on the first line.

//I concatenated the Math classes with the original game file
//so the example can run, this part is irrelevant, please, 
//scroll down, until you reach the main() function

function Vector3(x, y, z) {
	this.x = x;
	this.y = y;
	this.z = z;
}

Vector3.prototype.mag = function() {
	return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}

Vector3.prototype.distance = function(vec) {
	if (vec instanceof Vector3)
		return Math.sqrt((this.x - vec.x) * (this.x - vec.x) + (this.y - vec.y) * (this.y - vec.y) + (this.z - vec.z) * (this.z - vec.z));
}

Vector3.prototype.add = function(vec) {
	if (vec instanceof Vector3) {
		this.x += vec.x;
		this.y += vec.y;
		this.z += vec.z;
	}
	return this;
}

Vector3.prototype.subtract = function(vec) {
	if (vec instanceof Vector3) {
		this.add(vec.negate);
	}
	return this;
}

Vector3.prototype.negate = function() {
	this.multiply(-1);
	return this;
}

Vector3.prototype.multiply = function(n) {
	this.x *= n;
	this.y *= n;
	this.z *= n;
	return this;
}

Vector3.prototype.divide = function(n) {
	if (n != 0) {
		this.multiply(1 / n);
	}
	return this;
}

Vector3.prototype.normalize = function() {
	this.divide(this.mag());
	return this;
}

Vector3.cross = function(vec1, vec2) {
	if (vec1 instanceof Vector3 && vec2 instanceof Vector3) {
		var x = vec1.y * vec2.z - vec1.z * vec2.y;
		var y = vec1.z * vec2.x - vec1.x * vec2.z;
		var z = vec1.x * vec2.y - vec1.y * vec2.x;
		return new Vector3(x, y, z);
	}
}

Vector3.dot = function(vec1, vec2) {
	if (vec1 instanceof Vector3 && vec2 instanceof Vector3) {
		return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
	}
}

function Matrix3() {
	this.data = [];
	for (var i = 0; i < 9; i++) {
		this.data[i] = 0;
	}
}

Matrix3.prototype.setIdentity = function() {
	this.data[0 + 0 * 3] = 1;
	this.data[1 + 1 * 3] = 1;
	this.data[2 + 2 * 3] = 1;
}

Matrix3.prototype.add = function(mat) {
	if (mat instanceof Matrix3) {
		for (var i = 0; i < 9; i++) {
			this.data[i] += mat.data[i];
		}
	}
}

Matrix3.prototype.subtract = function(mat) {
	if (mat instanceof Matrix3)
		this.add(mat.negate());
}

Matrix3.prototype.multiplyScalar = function(n) {
	for (var i = 0; i < 9; i++) {
		this.data[i] *= n;
	}
}

Matrix3.prototype.negate = function() {
	this.multiply(-1);
}

Matrix3.prototype.multiplyVector = function(vec) {
	if (vec instanceof Vector3) {
		var x = this.data[0 + 0 * 3] * vec.x + this.data[0 + 1 * 3] * vec.y + this.data[0 + 2 * 3] * vec.z;
		var y = this.data[1 + 0 * 3] * vec.x + this.data[1 + 1 * 3] * vec.y + this.data[1 + 2 * 3] * vec.z;
		var z = this.data[2 + 0 * 3] * vec.x + this.data[2 + 1 * 3] * vec.y + this.data[2 + 2 * 3] * vec.z;
		
		return new Vector3(x, y, z);
	}
}

Matrix3.prototype.multiplyMatrix = function(mat) {
	if (mat instanceof Matrix3) {
		var result = new Matrix3();
		for (var y = 0; y < 3; y++) {
			for (var x = 0; x < 3; x++) {
				var sum = 0;
				for (var e = 0; e < 3; e++) {
					sum += this.data[e + y * 3] * mat.data[x + e * 3];
				}
				result.data[x + y * 3] = sum;
			}
		}
		return result;
	}
}

Matrix3.prototype.transpose = function() {
	var result = new Matrix3();
	for (var x = 0; x < 3; x++) {
		for (var y = 0; y < 3; y++) {
			result.data[y + x * 3] = this.data[x + y * 3]; 
		}
	}
	return result;
}

Matrix3.translate = function(vec) {
	var result = new Matrix3();
	result.setIdentity();
	if (vec instanceof Vector2) {
		result.data[2 + 0 * 3] = vec.x;
		result.data[2 + 1 * 3] = vec.y;
	} else if (vec instanceof Vector3) {
		result.data[2 + 0 * 3] = vec.x;
		result.data[2 + 1 * 3] = vec.y;
		result.data[2 + 2 * 3] = vec.z;
	}
	return result;
}

Matrix3.rotate = function(angle, x, y, z) {
	var result = new Matrix3();
	result.setIdentity();
	
	var cos = Math.cos(angle);
	var sin = Math.sin(angle);
	var omc = 1 - cos;
	
	result.data[0 + 0 * 3] = x * omc + cos;
	result.data[1 + 0 * 3] = y * x * omc + z * sin;
	result.data[2 + 0 * 3] = x * z * omc - y * sin;
	
	result.data[0 + 1 * 3] = x * y * omc - z * sin;
	result.data[1 + 1 * 3] = y * omc + cos;
	result.data[2 + 1 * 3] = y * z * omc + x * sin;
	
	result.data[0 + 2 * 3] = x * z * omc + y * sin;
	result.data[1 + 2 * 3] = y * z * omc - x * sin;
	result.data[2 + 2 * 3] = z * omc + cos;
	
	return result;
}

Matrix3.scale = function(vec) {
	var result = new Matrix3();
	result.setIdentity();
	
	if (vec instanceof Vector3) {	
		result.data[0 + 0 * 3] = vec.x;
		result.data[1 + 1 * 3] = vec.y;
		result.data[2 + 2 * 3] = vec.z;
		
		return result;
	} else if (vec instanceof Vector2) {
		result.data[0 + 0 * 3] = vec.x;
		result.data[1 + 1 * 3] = vec.y;
		
		return result;
	}
}

//This is what matters

var c, ctx;

var points = [];

var width = 120;

var numOfPoints = 100;
var testCases = 50;

var angle = new Vector3(0, 0, 0);
var angleSpeed = new Vector3(Math.random() * 0.06 - 0.03, Math.random() * 0.06 - 0.03, Math.random() * 0.06 - 0.03);

function main() {
	c = document.getElementById("canvas");
	c.width = 300;
	c.height = 300;
	ctx = c.getContext("2d");
	
	ctx.fillStyle = "rgb(255, 30, 30)";
	
	for (var i = 0; i < numOfPoints; i++) {
		var buf = [];
		for (var j = 0; j < testCases; j++) {
			var v = new Vector3(Math.random() * 4 - 2, Math.random() * 4 - 2, Math.random() * 4 - 2);
			v.normalize().multiply(width);
			buf.push(v);
		}
		var currentSum = 0;
		var currentHighest = 0;
		
		for (var k = 0; k < testCases; k++) {
			var sum = 0;
			var p = buf[k];
			for (var l of points) {
				sum += p.distance(l);
			}
			if (sum > currentSum) {
				currentSum = sum;
				currentHighest = k;
			}
		}
		points.push(buf[currentHighest]);
	}
	
	loop();
}

function loop() {
	update();
	render();
	window.requestAnimationFrame(loop);
}

function update() {
	angle.add(angleSpeed);
}

function render() {
	ctx.clearRect(0, 0, c.width, c.height);
	
	var rotation1 = Matrix3.rotate(angle.x, 1, 0, 0);
	var rotation2 = Matrix3.rotate(angle.y, 0, 1, 0);
	var rotation3 = Matrix3.rotate(angle.z, 0, 0, 1);
	var rotation = rotation1.multiplyMatrix(rotation2.multiplyMatrix(rotation3));
	
	for (var p of points) {
		p = rotation.multiplyVector(p);
		ctx.beginPath();
		ctx.arc(p.x + c.width / 2, p.y + c.height / 2, 2, 0, 2 * Math.PI);
		ctx.closePath();
		ctx.fill();
	}
}

main();
* {
	margin: 0;
	padding: 0;
}

canvas {
	background-color: rgb(20, 20, 20);
}
<html>
	<head>
		<link rel="stylesheet" href="style.css" />
	</head>
	<body>
		<canvas id="canvas">
		</canvas>
	</body>
</html>

\$\endgroup\$
  • \$\begingroup\$ 8 space indentation? That's a bit verbose of a read. I generally stick to 2 spaces unless I'm collaborating on a project that's already established a norm of 4 spaces. Edit upon checking the post I noticed that's actually a tab character. You generally want to avoid those as well. \$\endgroup\$ – Patrick Roberts Mar 20 '16 at 16:35
  • \$\begingroup\$ @PatrickRoberts I use spaces usually, but I'm not on my PC, where my IDEs are, and I didn't wanted to download one, so i used notepad++, putting tabs was a bit easier than 4 spaces. \$\endgroup\$ – Bálint Mar 20 '16 at 16:44
  • \$\begingroup\$ TABS FOR LIFE!! \$\endgroup\$ – Almo Jan 26 '17 at 22:28
1
\$\begingroup\$
;(function(){
  // Everything inside here
}());

Consider wrapping your code in a closure. That way, variables and functions declared aren't sitting on the global scope. Also, don't forget using var (or let and const if dealing with ES6). Otherwise, the variable will shoot up to the global scope and be declared there (or override an existing one of the same name).

var angleSpeed = new Vector3(Math.random() * 0.06 - 0.03, Math.random() * 0.06 - 0.03, Math.random() * 0.06 - 0.03);

Avoid "magic numbers. In this case, 0.06 and 0.03 have no meaning. Someone reading the code would be confused as to what they are. Suggesting you put them in variables so that they at least have a name. Same goes for the rest of the single-character variables you're using.

c = document.getElementById("canvas");
ctx = c.getContext("2d");

c isn't a very meaningful variable name, and so is ctx. Code is for people, make them mean something at least. Size doesn't matter (for now), readability is. Size can be solved by minifiers, readability can only be solved by writing it verbosely.

function loop() {
  update();
  render();
  window.requestAnimationFrame(loop);
}

First, I would suggest scheduling the next call first, before calling update and render to prevent scheduling from being dragged behind by slow operations before it.

Next, for this simple operation, timing isn't really critical. But if it is, consider using delta time or animation scaling so that framerate doesn't affect the rate of the animation. That way, say if the sphere fully rotates in a second at 60fps, it will still fully rotate in one second even if the framerate dropped to 40fps.

* {
    margin: 0;
    padding: 0;
}

Suggesting you avoid this. Using * means selecting all elements and removing their margins and paddings. Not only is it slow because it gathers all elements and applies the style to it, it's also bad when you embed your CSS into an existing page as it may accidentally remove paddings and margins for all elements, yours or otherwise.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.