Summary Files Commits Branches @commit @diff @stats
path: root / Year 2 / Computer Graphics with WebGL / Project / camera-movement.js
blob: 842fbd2c79f40e56b3d0b001eddfd50e9626b899 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/* Logic for moving around with mouse and keyboard */

/* Helpers */

// Since with the mouse cursor we change the looking direction vertically or horizontally
// it's significantly simpler to just use spherical coordinates and change the angles upon
// mouse movement.
// Since these are looking directions, the sphere radius is one (meaning the spherical coordinates
// will always be converted into a cartesian unit vector).
// The downside is that we have additional calculations to make, but it's worth it.
SphericalCoord = function(theta, phi) {
	this.fulltheta = this.theta = theta;
	this.phi = phi;
	this.recalculateCartesian();
}
SphericalCoord.prototype.recalculateCartesian = function() {
	return this.cartesian = [Math.sin(this.theta) * Math.sin(this.phi),
	                         Math.cos(this.theta),
	                         Math.sin(this.theta) * Math.cos(this.phi)];
}
SphericalCoord.prototype.addAngles = function(theta, phi) {
	// I figured this out on my own :)
	// Theta is in the range [0, pi), however in a sphere we have 2pi degrees.
	// We keep a "full" theta, which is in the range [0, 2pi), and then adjust normal theta
	// accordingly. With just normal theta, after a positive value is passed (for example)
	// we wouldn't know in which direction to rotate (or more accurately, we would always
	// increase theta so it goes the the "south" pole, which is bad, since increasing the
	// value should just continue rotating the point, like with phi).
	// When you "overextend" past pi, so when you need to reach the other hemisphere, between
	// pi and 2pi, you need to rotate phi by 180 degrees (2pi), so theta is acting on the other
	// hemisphere, and then we need to remove from 2pi the "leftover" amount, since our theta
	// would signify the "reverse" angle than the one we want.

	var before = this.fulltheta <= Math.PI;
	this.fulltheta = (this.fulltheta + theta) % (2 * Math.PI);
	this.theta = (this.fulltheta <= Math.PI) ? this.fulltheta : -this.fulltheta;

	// We check if fulltheta passed through the 180 degree/pi border
	if (before && this.fulltheta > Math.PI) {
		this.phi = (this.phi + Math.PI) % (2 * Math.PI);
	}
	else if (!before && this.fulltheta <= Math.PI) {
		this.phi = (this.phi - Math.PI) % (2 * Math.PI);
	}

	this.phi = (this.phi + phi) % (2 * Math.PI);

	this.recalculateCartesian();
	return this;
}
SphericalCoord.prototype.toCartesian = function() {
	return this.cartesian;
}

function vecSum(a, b) {
	return [a[0]+b[0], a[1]+b[1], a[2]+b[2]];
}
function vecSub(a, b) {
	return [a[0]-b[0], a[1]-b[1], a[2]-b[2]];
}

/* Variables */

const MOVE_SPEED = 1.5;
const ROTATION_SPEED = 3;

var pos     = [1000, 0, 650];

function initCameraVectors() {
	forward = new SphericalCoord(Math.PI / 2, 0);
	up      = new SphericalCoord(0, 0);
	  // this vector can be calculated from forward and up, but this way we improve speed
	left    = new SphericalCoord(Math.PI / 2,  Math.PI / 2);
}
initCameraVectors();

var isPaused = true;

/* Navigation keys */

// Since I'm lazy, there is a different function, changing pos, for every key
// so when a key is pressed, we'll execute the action on the same index
var pressedKeys = [ false, false, false, false, false, false ];
var keyAction = [
	() => { pos = vecSum(pos, up.toCartesian()); },      // ' '
	() => { pos = vecSub(pos, up.toCartesian()); },      // 'Shift'
	() => { pos = vecSum(pos, forward.toCartesian()); }, // 'w'
	() => { pos = vecSub(pos, forward.toCartesian()); }, // 's'
	() => { pos = vecSum(pos, left.toCartesian()); },    // 'a'
	() => { pos = vecSub(pos, left.toCartesian()); },    // 'd'
];
document.addEventListener('keydown', (event) => {
	if (isPaused) return;

	switch (event.key) {
		case ' '    : pressedKeys[0] = true; break;
		case 'Shift': pressedKeys[1] = true; break;
		case 'w'    : pressedKeys[2] = true; break;
		case 's'    : pressedKeys[3] = true; break;
		case 'a'    : pressedKeys[4] = true; break;
		case 'd'    : pressedKeys[5] = true; break;
	}
});
document.addEventListener('keyup', (event) => {
	if (isPaused) return;

	switch (event.key) {
		case ' '    : pressedKeys[0] = false; break;
		case 'Shift': pressedKeys[1] = false; break;
		case 'w'    : pressedKeys[2] = false; break;
		case 's'    : pressedKeys[3] = false; break;
		case 'a'    : pressedKeys[4] = false; break;
		case 'd'    : pressedKeys[5] = false; break;
	}
});

/* Mouse movement */

var onlyHorizontal = false;
var onlyVertical = false;
function updateLookingDirection(e) {
	if (isPaused) return;

	// Get mouse movement
	var vertical   = (1-onlyHorizontal) * (e.movementY / canvas.offsetHeight) * ROTATION_SPEED;
	var horizontal = (1-onlyVertical)   * (-e.movementX / canvas.offsetWidth) * ROTATION_SPEED;

	forward.addAngles(vertical, horizontal);
	up.addAngles(vertical, horizontal);
	left.addAngles(vertical, horizontal);
}
document.addEventListener('mousemove', updateLookingDirection);
document.addEventListener('pointerlockchange', () => {
	isPaused = document.pointerLockElement !== canvas;
	menu.classList.toggle('hide');
});

function mouseInit() {
	// Capture mouse pointer when appropriate
	paused = document.getElementById('paused');

	canvas.addEventListener('click', async () => {
		if (isPaused)
			await canvas.requestPointerLock();
	});
	paused.addEventListener('click', async () => {
		if (isPaused)
			await canvas.requestPointerLock();
	});
	document.addEventListener('keyup', (e) => {
		if (e.key === 'Escape') paused.click();
	});
}

/* On every drawFrame */
coordsElem = undefined;

function updateCamera() {
	// Move camera position
	for (var i = 0; i < pressedKeys.length; i++)
		if (pressedKeys[i]) keyAction[i]();
	lookAt(pos, vecSum(pos, forward.toCartesian()), up.toCartesian());

	// Update "external" variables
	if (coordsElem) coordsElem.innerText = 'X: '+pos[0].toFixed(2)+' Y: '+pos[1].toFixed(2)+' Z: '+pos[2].toFixed(2);
	gl.uniform3fv(uEye, pos);
}