diff options
[WGL/P] Added a lot of comments and reworked formatting of certain code
1 files changed, 164 insertions, 0 deletions
| -rw-r--r-- | Year 2/Computer Graphics with WebGL/Project/camera-movement.js | 164 |
diff --git a/Year 2/Computer Graphics with WebGL/Project/camera-movement.js b/Year 2/Computer Graphics with WebGL/Project/camera-movement.js new file mode 100644 index 0000000..f06db00 --- /dev/null +++ b/Year 2/Computer Graphics with WebGL/Project/camera-movement.js @@ -0,0 +1,164 @@ +/* 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]; + +var forward = new SphericalCoord(Math.PI / 2, 0); +var up = new SphericalCoord(0, 0); + // this vector can be calculated from forward and up, but this way we improve speed +var left = new SphericalCoord(Math.PI / 2, Math.PI / 2); + +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]+' Y: '+pos[1]+' Z: '+pos[2]; + gl.uniform3fv(uEye, pos); +} |
