initial commit

This commit is contained in:
Paul Saikko 2016-06-12 01:40:10 +03:00
commit ed4fd2a1ac
9 changed files with 515 additions and 0 deletions

21
game.js Normal file
View File

@ -0,0 +1,21 @@
var raycast = raycast || {};
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element){
window.setTimeout(callback, 1000 / 60);
};
})();
function start() {
document.onkeyup = raycast.keyhandler.onKeyup;
document.onkeydown = raycast.keyhandler.onKeydown;
var textureFiles = ["img/brick.png", "img/ground.png", "img/sky.png"];
raycast.texture.initiateLoad(textureFiles, raycast.engine.start);
};
window.onload = start;

BIN
img/brick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
img/ground.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
img/sky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

65
keyhandler.js Normal file
View File

@ -0,0 +1,65 @@
var raycast = raycast || {};
raycast.keyhandler = (function () {
var codes = {
up: 38,
w: 87,
down: 40,
left: 37,
a: 65,
right: 39,
d: 68,
space: 32,
ctrl: 17,
esc: 27
};
var state = new Array();
var lastState = new Array();
for (var i = 0; i < 255; i++) {
state[i] = false;
lastState[i] = false;
}
onKeyup = function (e) {
state[e.which] = false;
if (isUsedKey(e.which))
e.preventDefault();
}
onKeydown = function (e) {
state[e.which] = true;
if (isUsedKey(e.which))
e.preventDefault();
}
function isUsedKey(keycode) {
for (var key in codes) {
if (codes[key] == keycode) {
return true;
}
}
return false;
}
isKeydown = function(keyname) {
return state[codes[keyname]];
}
isKeypress = function(keyname) {
return state[codes[keyname]] && !lastState[codes[keyname]];
}
tick = function() {
lastState = state.slice();
}
return {
onKeyup: onKeyup,
onKeydown: onKeydown,
isKeydown: isKeydown,
isKeypress: isKeypress,
tick: tick
};
})();

17
raycast.html Normal file
View File

@ -0,0 +1,17 @@
<html>
<head>
<meta charset="UTF-8">
</head>
<body bgcolor="999999">
<div id="canvasdiv">
<canvas id="viewport" width="800" height="600"></canvas>
</div>
<script src="keyhandler.js"></script>
<script src="texture.js"></script>
<script src="raycast.js"></script>
<script src="game.js"></script>
</body>
</html>

349
raycast.js Normal file
View File

@ -0,0 +1,349 @@
// http://lodev.org/cgtutor/raycasting.html
var raycast = raycast || {};
raycast.engine = (function () {
var canvas = document.getElementById("viewport");
var g = canvas.getContext("2d");
var filtering = false;
var mapWidth = 24,
mapHeight = 24,
texHeight = 64,
texWidth = 64;
var texture;
function initTexture() {
texture = raycast.texture.getTextures();
console.log(texture);
texture.push([]);
for(var x = 0; x < texWidth; x++) {
for(var y = 0; y < texHeight; y++) {
var xorcolor = (x * 256 / texWidth) ^ (y * 256 / texHeight);
var d = Math.sqrt((texWidth/2 - x)*(texWidth/2 - x) + (texHeight/2 - y)*(texHeight/2 - y));
var sincolor = 256 * (1 + Math.sin(d/2)) / 2;
texture[3][texWidth * y + x] = [xorcolor, 0, sincolor]; // blue sine pattern
}
}
}
/*
texture[0] = raycast.texture.load("ground");
texture[1] = raycast.texture.load("brick");
texture[2] = raycast.texture.load("sky");
*/
var worldMap = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,1,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,1,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
];
var posX = 2, posY = 2,
dirX = -1, dirY = 0,
planeX = 0, planeY = 0.66,
time = Date.now(), oldTime = Date.now();
var moveSpeed, rotSpeed;
var w = canvas.width,
h = canvas.height;
function verLine(arr, x, yStart, yEnd, color){
//console.log(x, yStart, yEnd);
for (var y = yStart | 0; y < yEnd | 0; y++) {
var i = 4 * (w * y) + 4 * x;
arr[i + 0] = color[0];
arr[i + 1] = color[1];
arr[i + 2] = color[2];
arr[i + 3] = 255;
}
};
imagedata = g.getImageData(0,0,w,h);
var buffer = imagedata.data;
var keys = raycast.keyhandler;
function input() {
if (keys.isKeydown("up")) {
if(worldMap[(posX + dirX * moveSpeed) | 0][posY | 0] == 0) posX += dirX * moveSpeed;
if(worldMap[posX | 0][(posY + dirY * moveSpeed) | 0] == 0) posY += dirY * moveSpeed;
}
//move backwards if no wall behind you
if (keys.isKeydown("down")) {
if(worldMap[(posX - dirX * moveSpeed) | 0][posY | 0] == 0) posX -= dirX * moveSpeed;
if(worldMap[posX | 0][(posY - dirY * moveSpeed) | 0] == 0) posY -= dirY * moveSpeed;
}
if (keys.isKeydown("right")) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * Math.cos(-rotSpeed) - dirY * Math.sin(-rotSpeed);
dirY = oldDirX * Math.sin(-rotSpeed) + dirY * Math.cos(-rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * Math.cos(-rotSpeed) - planeY * Math.sin(-rotSpeed);
planeY = oldPlaneX * Math.sin(-rotSpeed) + planeY * Math.cos(-rotSpeed);
}
if (keys.isKeydown("left")) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * Math.cos(rotSpeed) - dirY * Math.sin(rotSpeed);
dirY = oldDirX * Math.sin(rotSpeed) + dirY * Math.cos(rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * Math.cos(rotSpeed) - planeY * Math.sin(rotSpeed);
planeY = oldPlaneX * Math.sin(rotSpeed) + planeY * Math.cos(rotSpeed);
}
if (keys.isKeypress("d"))
filtering = !filtering;
}
function draw() {
for(var x = 0; x < w; x++) {
var cameraX = 2 * x / w - 1,
rayPosX = posX,
rayPosY = posY,
rayDirX = dirX + planeX * cameraX,
rayDirY = dirY + planeY * cameraX;
var mapX = rayPosX | 0,
mapY = rayPosY | 0;
var deltaDistX = Math.sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX)),
deltaDistY = Math.sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
var stepX,
stepY,
sideDistX,
sideDistY;
if (rayDirX < 0) {
stepX = -1;
sideDistX = (rayPosX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (rayPosY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY;
}
var side, hit = 0;
// DDA
while (hit == 0) {
side = sideDistX > sideDistY;
if (side == 0) {
sideDistX += deltaDistX;
mapX += stepX;
} else {
sideDistY += deltaDistY;
mapY += stepY;
}
if (worldMap[mapX][mapY] > 0) {
hit = 1;
}
}
var perpWallDist;
if (side == 0)
perpWallDist = Math.abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX);
else
perpWallDist = Math.abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY);
//Calculate height of line to draw on screen
var lineHeight = Math.abs((h / perpWallDist) | 0);
//calculate lowest and highest pixel to fill in current stripe
var drawStart = ((h - lineHeight) / 2) | 0;
if(drawStart < 0)
drawStart = 0;
var drawEnd = ((h + lineHeight) / 2) | 0;
if(drawEnd >= h)
drawEnd = h - 1;
var wallX; // the exact value where the wall was hit
if (side == 1)
wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX;
else
wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY;
wallX -= wallX | 0;
var texX = (wallX * texWidth)/* | 0*/;
if(side == 0 && rayDirX > 0)
texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0)
texX = texWidth - texX - 1;
var shade = (side == 1 ? 0.6: 1);
var wallTex = texture[worldMap[mapX][mapY] - 1];
for (var y = drawStart; y < drawEnd; y++) {
var d = (y * 256 - h * 128 + lineHeight * 128) | 0;
var texY = ((d * texHeight) / (lineHeight * 256))/* | 0*/;
if (texY < 0) texY = 0;
var color;
if (filtering) {
var ty1 = texY | 0;
var ty2 = (ty1 + 1) % texHeight;
var tx1 = texX | 0;
var tx2 = (tx1 + 1) % texWidth;
var xf = texX - (texX | 0);
var yf = texY - (texY | 0);
color = [0,0,0];
var c1 = wallTex[texWidth * ty1 + tx1];
var c2 = wallTex[texWidth * ty1 + tx2];
var c3 = wallTex[texWidth * ty2 + tx1];
var c4 = wallTex[texWidth * ty2 + tx2];
color[0] = (c1[0]*(1-xf)*(1-yf) + c2[0]*xf*(1-yf) + c3[0]*(1-xf)*yf + c4[0]*xf*yf) | 0;
color[1] = (c1[1]*(1-xf)*(1-yf) + c2[1]*xf*(1-yf) + c3[1]*(1-xf)*yf + c4[1]*xf*yf) | 0;
color[2] = (c1[2]*(1-xf)*(1-yf) + c2[2]*xf*(1-yf) + c3[2]*(1-xf)*yf + c4[2]*xf*yf) | 0;
} else {
texX |= 0;
texY |= 0;
color = wallTex[texHeight * texY + texX];
}
var i = 4 * (w * y) + 4 * x;
buffer[i + 0] = color[0] * shade;
buffer[i + 1] = color[1] * shade;
buffer[i + 2] = color[2] * shade;
buffer[i + 3] = 255;
}
var floorXWall, floorYWall; //x, y position of the floor texel at the bottom of the wall
//4 different wall directions possible
if (side == 0 && rayDirX > 0) {
floorXWall = mapX;
floorYWall = mapY + wallX;
} else if (side == 0 && rayDirX < 0) {
floorXWall = mapX + 1.0;
floorYWall = mapY + wallX;
} else if (side == 1 && rayDirY > 0) {
floorXWall = mapX + wallX;
floorYWall = mapY;
} else /* side == 1 && rayDirY > 0*/{
floorXWall = mapX + wallX;
floorYWall = mapY + 1.0;
}
var currentDist;
var distWall = perpWallDist;
var distPlayer = 0.0;
if (drawEnd < 0) drawEnd = h; //becomes < 0 when the integer overflows
var ceilTex = texture[2];
var floorTex = texture[1];
//draw the floor from drawEnd to the bottom of the screen
for(var y = drawEnd; y < h; y++)
{
currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
var weight = (currentDist - distPlayer) / (distWall - distPlayer);
var nextWeight = (h / (2.0 * (y+1) - h)) / distWall;
var currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
var currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
var floorTexX = (currentFloorX * texWidth) % texWidth;
var floorTexY = (currentFloorY * texHeight) % texHeight;
if (floorTexX < 0) floorTexX = 0;
if (floorTexY < 0) floorTexY = 0;
var color;
if (filtering) {
ty1 = floorTexY | 0;
ty2 = (ty1 + 1) % texHeight;
tx1 = floorTexX | 0;
tx2 = (tx1 + 1) % texWidth;
xf = floorTexX - (floorTexX | 0);
yf = floorTexY - (floorTexY | 0);
color = [0,0,0];
c1 = floorTex[texWidth * ty1 + tx1];
c2 = floorTex[texWidth * ty1 + tx2];
c3 = floorTex[texWidth * ty2 + tx1];
c4 = floorTex[texWidth * ty2 + tx2];
color[0] = (c1[0]*(1-xf)*(1-yf) + c2[0]*xf*(1-yf) + c3[0]*(1-xf)*yf + c4[0]*xf*yf) | 0;
color[1] = (c1[1]*(1-xf)*(1-yf) + c2[1]*xf*(1-yf) + c3[1]*(1-xf)*yf + c4[1]*xf*yf) | 0;
color[2] = (c1[2]*(1-xf)*(1-yf) + c2[2]*xf*(1-yf) + c3[2]*(1-xf)*yf + c4[2]*xf*yf) | 0;
} else {
color = floorTex[texWidth * (floorTexY|0) + (floorTexX|0)];
}
//floor
i = 4 * (w * y) + 4 * x;
buffer[i+0] = (color[0])/2;
buffer[i+1] = (color[1])/2;
buffer[i+2] = (color[2])/2;
buffer[i+3] = 255;
if (filtering) {
color = [0,0,0];
c1 = ceilTex[texWidth * ty1 + tx1];
c2 = ceilTex[texWidth * ty1 + tx2];
c3 = ceilTex[texWidth * ty2 + tx1];
c4 = ceilTex[texWidth * ty2 + tx2];
color[0] = (c1[0]*(1-xf)*(1-yf) + c2[0]*xf*(1-yf) + c3[0]*(1-xf)*yf + c4[0]*xf*yf) | 0;
color[1] = (c1[1]*(1-xf)*(1-yf) + c2[1]*xf*(1-yf) + c3[1]*(1-xf)*yf + c4[1]*xf*yf) | 0;
color[2] = (c1[2]*(1-xf)*(1-yf) + c2[2]*xf*(1-yf) + c3[2]*(1-xf)*yf + c4[2]*xf*yf) | 0;
} else {
color = ceilTex[texWidth * (floorTexY|0) + (floorTexX|0)];
}
//ceiling (symmetrical!)
i = 4 * (w * (h - y - 1)) + 4 * x;
buffer[i+0] = color[0]/2;
buffer[i+1] = color[1]/2;
buffer[i+2] = color[2]/2;
buffer[i+3] = 255;
}
}
oldTime = time;
time = Date.now()
var frameTime = (time - oldTime) / 1000.0; //frameTime is the time this frame has taken, in seconds
//speed modifiers
moveSpeed = frameTime * 5.0; //the constant value is in squares/second
rotSpeed = frameTime * 3.0; //the constant value is in radians/second
g.putImageData(imagedata, 0, 0);
g.font = "bold 30pt Monospace";
g.fillText(""+((1000 / (time - oldTime))|0), 0, 30);
};
function tick() {
draw();
input();
keys.tick();
window.requestAnimFrame(tick);
//window.setTimeout(tick, 1);
};
function start() {
initTexture();
tick();
}
return {start: start}
}());

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

63
texture.js Normal file
View File

@ -0,0 +1,63 @@
var raycast = raycast || {};
raycast.texture = (function () {
function load(id) {
var canvas = document.createElement('canvas');
var image = document.getElementById(id);
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
var imagedata = ctx.getImageData(0, 0, image.width, image.height);
var rgbArray = new Array(image.width * image.height);
for( var i = 0; i < image.width * image.height; i++) {
rgbArray[i] = [imagedata.data[4*i], imagedata.data[4*i+1], imagedata.data[4*i+2]];
}
image.width = 0;
image.height = 0;
return rgbArray;
}
var images = [];
function initiateLoad(textures, onSuccess) {
var n = textures.length;
var counter = 0;
var callback = function() {
counter++;
console.log(counter+" of "+n+" textures received")
if (counter == n) onSuccess();
}
for (var i = 0; i < n; i++) {
var image = new Image()
image.onload = callback;
image.src = textures[i];
images.push(image);
}
}
function getTextures() {
var n = images.length;
var textures = [];
for (var i = 0; i < n; i++) {
var canvas = document.createElement('canvas');
var image = images[i];
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
var imagedata = ctx.getImageData(0, 0, image.width, image.height);
var rgbArray = new Array(image.width * image.height);
for(var j = 0; j < image.width * image.height; j++) {
rgbArray[j] = [imagedata.data[4*j], imagedata.data[4*j+1], imagedata.data[4*j+2]];
}
textures.push(rgbArray);
}
return textures;
}
return {load: load, initiateLoad: initiateLoad, getTextures: getTextures};
}());