Last active
August 29, 2015 14:05
-
-
Save svanellewee/d9f7e89c382f4e0d5922 to your computer and use it in GitHub Desktop.
Studying Lodev's tut (http://lodev.org/cgtutor/raycasting.html) and Playful Js's example (http://www.playfuljs.com/a-first-person-engine-in-265-lines/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var magnitude = function(xcomp,ycomp) { return Math.sqrt(xcomp*xcomp + ycomp*ycomp); }; | |
| var RESOLUTION = window.innerWidth/2; // 320; | |
| var HEIGHT = window.innerHeight/2; //480; | |
| var div = function(x,y){ return Math.floor(x/y) }; | |
| var mod | |
| var CIRCLE = Math.PI * 2; | |
| function Controls() { | |
| this.codes = { 37: 'left', 39: 'right', 38: 'forward', 40: 'backward' }; | |
| this.states = { 'left': false, 'right': false, 'forward': false, 'backward': false }; | |
| document.addEventListener('keydown', this.onKey.bind(this, true), false); | |
| document.addEventListener('keyup', this.onKey.bind(this, false), false); | |
| document.addEventListener('touchstart', this.onTouch.bind(this), false); | |
| document.addEventListener('touchmove', this.onTouch.bind(this), false); | |
| document.addEventListener('touchend', this.onTouchEnd.bind(this), false); | |
| } | |
| Controls.prototype.onTouch = function(e) { | |
| var t = e.touches[0]; | |
| this.onTouchEnd(e); | |
| if (t.pageY < window.innerHeight * 0.5) this.onKey(true, { keyCode: 38 }); | |
| else if (t.pageX < window.innerWidth * 0.5) this.onKey(true, { keyCode: 37 }); | |
| else if (t.pageY > window.innerWidth * 0.5) this.onKey(true, { keyCode: 39 }); | |
| }; | |
| Controls.prototype.onTouchEnd = function(e) { | |
| this.states = { 'left': false, 'right': false, 'forward': false, 'backward': false }; | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }; | |
| Controls.prototype.onKey = function(val, e) { | |
| var state = this.codes[e.keyCode]; | |
| if (typeof state === 'undefined') return; | |
| //console.log(state); | |
| this.states[state] = val; | |
| e.preventDefault && e.preventDefault(); | |
| e.stopPropagation && e.stopPropagation(); | |
| }; | |
| var worldMap = [ | |
| [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1], | |
| [1,0,0,0,0,0,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1], | |
| [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], | |
| [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] ]; | |
| // console.log(worldMap[0][0]); | |
| function Renderer () { | |
| var canvas = document.getElementById('display') | |
| this.ctx = canvas.getContext('2d'); | |
| this.width = canvas.width = window.innerWidth *0.5; | |
| this.height = canvas.height = window.innerHeight *0.5; | |
| }; | |
| var renderer = new Renderer(); | |
| var plane_vector_X = 0.0, plane_vector_Y = 0.66; //the 2d raycaster version of camera plane | |
| var main = function (posX, posY, view_direction_X, view_direction_Y) { | |
| // so FOV = 2 * atan (0.66/1) | |
| var ctx = renderer.ctx; | |
| for(var x = 0; x < RESOLUTION; x++) { | |
| var plane_vector_magnitude = 2*x/ (1.0*RESOLUTION) - 1 // -1 ... 0 ... 1 | |
| var rayPosX = 1.0*posX; | |
| var rayPosY = 1.0*posY; | |
| // vector addition! vec_ray = vec_dir + (vec_plane * plane_vector_magnitude); | |
| // apply defaults to decrypt! remember plane_vector_magnitude is varying -1..0..+1 | |
| var ray_direction_vector_X = 1.0* (view_direction_X + plane_vector_X * plane_vector_magnitude); | |
| var ray_direction_vector_Y = 1.0* (view_direction_Y + plane_vector_Y * plane_vector_magnitude); | |
| var map_X_index = Math.floor(posX); | |
| var map_Y_index = Math.floor(posY); | |
| // distance**2 = x**2 + y **2 | |
| // distance**2/y**2 = x**2/y**2 + 1 | |
| // distance/y = sqrt(x**2/y**2 + 1) | |
| var deltaDistX = Math.sqrt(1 + (ray_direction_vector_Y * ray_direction_vector_Y) / (ray_direction_vector_X * ray_direction_vector_X)); | |
| var deltaDistY = Math.sqrt(1 + (ray_direction_vector_X * ray_direction_vector_X) / (ray_direction_vector_Y * ray_direction_vector_Y)); | |
| var stepX, stepY; | |
| var sideDistX, sideDistY; | |
| if (ray_direction_vector_X < 0 ) { | |
| stepX = -1; | |
| sideDistX = (rayPosX - map_X_index) * deltaDistX; | |
| } else { | |
| stepX = 1; | |
| sideDistX = (map_X_index + 1.0 - rayPosX) * deltaDistX; | |
| } | |
| if (ray_direction_vector_Y < 0) { | |
| stepY = -1; | |
| sideDistY = (rayPosY - map_Y_index) * deltaDistY; | |
| } else { | |
| stepY = 1; | |
| sideDistY = (map_Y_index + 1.0 - rayPosY) * deltaDistY; | |
| } | |
| var side; | |
| var hit = 0; | |
| while (hit == 0) { | |
| if (sideDistX < sideDistY) { | |
| sideDistX +=deltaDistX; | |
| map_X_index += stepX; | |
| map_X_index = Math.floor(map_X_index) | |
| side = 0; | |
| } else { | |
| sideDistY += deltaDistY; | |
| map_Y_index += stepY | |
| map_Y_index = Math.floor(map_Y_index) | |
| side = 1; | |
| } | |
| if (worldMap[map_X_index][map_Y_index] > 0) | |
| hit = 1; | |
| } | |
| var perpWallDist = 0; | |
| if (side == 0) { | |
| perpWallDist = Math.abs((map_X_index - rayPosX + (1 - stepX) / 2.0) / ray_direction_vector_X); | |
| } else { | |
| perpWallDist = Math.abs((map_Y_index - rayPosY + (1 - stepY) / 2.0) / ray_direction_vector_Y); | |
| } | |
| var lineHeight = Math.abs(HEIGHT/perpWallDist); // how big is the wall slither? | |
| lineHeight = Math.floor(lineHeight); | |
| var drawStart = -lineHeight / 2 + HEIGHT/2; | |
| drawStart = drawStart < 0 ? 0 : Math.floor(drawStart); | |
| var drawEnd = lineHeight / 2 + HEIGHT/2; | |
| drawEnd = drawEnd >= HEIGHT ? HEIGHT-1 : Math.ceil(drawEnd); | |
| var color = "#000000"; | |
| switch(worldMap[map_X_index][map_Y_index]) { | |
| case 1: color = "#FF00FF"; break; | |
| case 2: color = "#0000FF"; break; | |
| case 3: color = "#FF0000"; break; | |
| case 4: color = "#00FF00"; break; | |
| default: color = "#FFFFFF"; break; | |
| } | |
| ctx.fillStyle = color; | |
| ctx.fillRect(x, drawStart, 1, Math.abs(drawEnd-drawStart) ); | |
| } | |
| }; | |
| // init state | |
| var posX = 22.0, posY = 12.0; | |
| //var view_direction_X = -0.5, dirY = 0.5; | |
| var view_direction_X = -1.0, view_direction_Y = 0.0; | |
| var controls = new Controls(); | |
| var rotate = function(angle) { | |
| angle = angle % CIRCLE; | |
| var cos_a = Math.cos(angle); | |
| var sin_a = Math.sin(angle); | |
| var new_plane_X, new_plane_Y; | |
| var new_view_X, new_view_Y; | |
| new_view_X = view_direction_X*cos_a - view_direction_Y*sin_a; | |
| new_view_Y = view_direction_X*sin_a + view_direction_Y*cos_a; | |
| new_plane_X = plane_vector_X*cos_a - plane_vector_Y*sin_a; | |
| new_plane_Y = plane_vector_X*sin_a + plane_vector_Y*cos_a; | |
| plane_vector_X = new_plane_X; | |
| plane_vector_Y = new_plane_Y; | |
| view_direction_X = new_view_X; | |
| view_direction_Y = new_view_Y; | |
| } | |
| var walk = function(distance) { | |
| var dx = view_direction_X * distance; | |
| var dy = view_direction_Y * distance; | |
| posX += dx; | |
| posY += dy; | |
| } | |
| var update = function(seconds) { | |
| // console.log(seconds,controls.states); | |
| if (controls.states.left) rotate(Math.PI * seconds); | |
| if (controls.states.right) rotate(-Math.PI * seconds); | |
| if (controls.states.forward) walk(3 * seconds); | |
| if (controls.states.backward) walk(-3 * seconds); | |
| renderer.ctx.clearRect(0,0,RESOLUTION, HEIGHT); | |
| main(posX,posY, view_direction_X, view_direction_Y); | |
| } | |
| var lastTime; | |
| var frame = function(time) { | |
| var seconds = (time - lastTime) / 1000; | |
| lastTime = time; | |
| if (seconds < 0.2) update(seconds); | |
| requestAnimationFrame(frame); | |
| } | |
| frame(0) | |
| //main(posX,posY, view_direction_X, view_direction_Y); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // the code required for demo, but not the point of the tut | |
| var CIRCLE = Math.PI * 2; | |
| var MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) | |
| function Controls() { | |
| this.codes = { 37: 'left', 39: 'right', 38: 'forward', 40: 'backward' }; | |
| this.states = { 'left': false, 'right': false, 'forward': false, 'backward': false }; | |
| document.addEventListener('keydown', this.onKey.bind(this, true), false); | |
| document.addEventListener('keyup', this.onKey.bind(this, false), false); | |
| document.addEventListener('touchstart', this.onTouch.bind(this), false); | |
| document.addEventListener('touchmove', this.onTouch.bind(this), false); | |
| document.addEventListener('touchend', this.onTouchEnd.bind(this), false); | |
| } | |
| Controls.prototype.onTouch = function(e) { | |
| var t = e.touches[0]; | |
| this.onTouchEnd(e); | |
| if (t.pageY < window.innerHeight * 0.5) this.onKey(true, { keyCode: 38 }); | |
| else if (t.pageX < window.innerWidth * 0.5) this.onKey(true, { keyCode: 37 }); | |
| else if (t.pageY > window.innerWidth * 0.5) this.onKey(true, { keyCode: 39 }); | |
| }; | |
| Controls.prototype.onTouchEnd = function(e) { | |
| this.states = { 'left': false, 'right': false, 'forward': false, 'backward': false }; | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }; | |
| Controls.prototype.onKey = function(val, e) { | |
| var state = this.codes[e.keyCode]; | |
| if (typeof state === 'undefined') return; | |
| this.states[state] = val; | |
| e.preventDefault && e.preventDefault(); | |
| e.stopPropagation && e.stopPropagation(); | |
| }; | |
| function Player(x, y, direction) { | |
| this.x = x; | |
| this.y = y; | |
| this.direction = direction; | |
| this.weapon = null; | |
| try { | |
| this.weapon = new Bitmap('assets/knife_hand.png', 319, 320); | |
| }catch (e) { | |
| // ignore it! | |
| alert("NO PNG!"); | |
| } | |
| this.paces = 0; | |
| } | |
| Player.prototype.rotate = function(angle) { | |
| this.direction = (this.direction + angle + CIRCLE) % (CIRCLE); | |
| console.log("rotated to :"+this.direction) | |
| }; | |
| Player.prototype.walk = function(distance, map) { | |
| var dx = Math.cos(this.direction) * distance; | |
| var dy = Math.sin(this.direction) * distance; | |
| if (map && map.get(this.x + dx, this.y) <= 0) this.x += dx; | |
| if (map && map.get(this.x, this.y + dy) <= 0) this.y += dy; | |
| this.paces += distance; | |
| console.log("walking to:"+this.paces); | |
| }; | |
| Player.prototype.update = function(controls, map, seconds) { | |
| if (controls.left) this.rotate(-Math.PI * seconds); | |
| if (controls.right) this.rotate(Math.PI * seconds); | |
| if (controls.forward) this.walk(3 * seconds, map); | |
| if (controls.backward) this.walk(-3 * seconds, map); | |
| }; | |
| function GameLoop() { | |
| this.frame = this.frame.bind(this); | |
| this.lastTime = 0; | |
| this.callback = function() {}; | |
| } | |
| GameLoop.prototype.start = function(callback) { | |
| this.callback = callback; | |
| requestAnimationFrame(this.frame); | |
| }; | |
| GameLoop.prototype.frame = function(time) { | |
| var seconds = (time - this.lastTime) / 1000; | |
| this.lastTime = time; | |
| if (seconds < 0.2) this.callback(seconds); | |
| requestAnimationFrame(this.frame); | |
| }; | |
| var display = document.getElementById('display'); | |
| var player = new Player(15.3, -1.2, Math.PI * 0.3); | |
| var controls = new Controls(); | |
| var loop = new GameLoop(); | |
| //map.randomize(); | |
| var map = null; | |
| loop.start(function frame(seconds) { | |
| //map.update(seconds); | |
| player.update(controls.states, map, seconds); | |
| //camera.render(player, map); | |
| }); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html> | |
| <head> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>canvasfun</title> | |
| </head> | |
| <body style='background: #000; margin: 0; padding: 0; width: 100%; height: 100%;'> | |
| <canvas id='display' width='1' height='1' style='width: 100%; height: 100%;' /> | |
| <script src="boilerplate.js"></script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment